Improve provider reliability and usage visibility
This commit is contained in:
@@ -857,26 +857,24 @@ impl BlocklistAIActionModel {
|
||||
});
|
||||
|
||||
let history_model = BlocklistAIHistoryModel::handle(ctx);
|
||||
ctx.subscribe_to_model(&history_model, |me, _, event, ctx| {
|
||||
match event {
|
||||
BlocklistAIHistoryEvent::RemoveConversation {
|
||||
conversation_id, ..
|
||||
}
|
||||
| BlocklistAIHistoryEvent::DeletedConversation {
|
||||
conversation_id, ..
|
||||
} => {
|
||||
ctx.subscribe_to_model(&history_model, |me, _, event, ctx| match event {
|
||||
BlocklistAIHistoryEvent::RemoveConversation {
|
||||
conversation_id, ..
|
||||
}
|
||||
| BlocklistAIHistoryEvent::DeletedConversation {
|
||||
conversation_id, ..
|
||||
} => {
|
||||
me.cleanup_conversation_state(*conversation_id, ctx);
|
||||
}
|
||||
BlocklistAIHistoryEvent::ClearedConversationsForTerminalSurface {
|
||||
cleared_conversation_ids,
|
||||
..
|
||||
} => {
|
||||
for conversation_id in cleared_conversation_ids {
|
||||
me.cleanup_conversation_state(*conversation_id, ctx);
|
||||
}
|
||||
BlocklistAIHistoryEvent::ClearedConversationsForTerminalSurface {
|
||||
cleared_conversation_ids,
|
||||
..
|
||||
} => {
|
||||
for conversation_id in cleared_conversation_ids {
|
||||
me.cleanup_conversation_state(*conversation_id, ctx);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
|
||||
Self {
|
||||
|
||||
@@ -55,7 +55,9 @@ use crate::ai::blocklist::prompt::prompt_alert::{PromptAlertEvent, PromptAlertVi
|
||||
use crate::ai::blocklist::usage::icon_for_context_window_usage;
|
||||
use crate::ai::blocklist::BlocklistAIInputModel;
|
||||
use crate::ai::execution_profiles::profiles::AIExecutionProfilesModel;
|
||||
use crate::ai::execution_profiles::AIExecutionProfileAppExt;
|
||||
use crate::ai::harness_availability::HarnessAvailabilityModel;
|
||||
use crate::ai::llms::LLMPreferences;
|
||||
use crate::ai::AIRequestUsageModel;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::auth::{AuthManager, AuthStateProvider};
|
||||
@@ -123,6 +125,16 @@ const FAST_FORWARD_LOCKED_TOOLTIP: &str =
|
||||
|
||||
const CLOUD_MODE_V2_FOOTER_GAP: f32 = 4.;
|
||||
|
||||
fn format_llm_token_count(tokens: u32) -> String {
|
||||
if tokens >= 1_000_000 {
|
||||
format!("{:.1}M", tokens as f64 / 1_000_000.0)
|
||||
} else if tokens >= 1_000 {
|
||||
format!("{:.1}k", tokens as f64 / 1_000.0)
|
||||
} else {
|
||||
tokens.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Voice input state for the CLI agent footer. Unlike the editor-based voice
|
||||
/// flow (which goes through Input → EditorView), this state is self-contained
|
||||
/// so that transcribed text can be written directly to the PTY.
|
||||
@@ -194,6 +206,8 @@ pub struct AgentInputFooter {
|
||||
nld_button: ViewHandle<ActionButton>,
|
||||
file_button: ViewHandle<ActionButton>,
|
||||
context_window_button: ViewHandle<ActionButton>,
|
||||
llm_context_usage_button: ViewHandle<ActionButton>,
|
||||
llm_cache_details_button: ViewHandle<ActionButton>,
|
||||
model_selector: ViewHandle<ProfileModelSelector>,
|
||||
environment_selector: Option<ViewHandle<EnvironmentSelector>>,
|
||||
handoff_environment_selector: ViewHandle<EnvironmentSelector>,
|
||||
@@ -610,6 +624,22 @@ impl AgentInputFooter {
|
||||
.with_tooltip_alignment(TooltipAlignment::Left)
|
||||
});
|
||||
|
||||
let llm_context_usage_button = ctx.add_typed_action_view(|_ctx| {
|
||||
ActionButton::new("Ctx —", AgentInputButtonTheme)
|
||||
.with_icon(Icon::ContextRemaining100)
|
||||
.with_tooltip("LLM context usage is available after the first model call")
|
||||
.with_size(button_size)
|
||||
.with_tooltip_alignment(TooltipAlignment::Left)
|
||||
});
|
||||
|
||||
let llm_cache_details_button = ctx.add_typed_action_view(|_ctx| {
|
||||
ActionButton::new("Cache —", AgentInputButtonTheme)
|
||||
.with_icon(Icon::LayersThree01)
|
||||
.with_tooltip("LLM cache details are available after the first model call")
|
||||
.with_size(button_size)
|
||||
.with_tooltip_alignment(TooltipAlignment::Left)
|
||||
});
|
||||
|
||||
let profile_model_selector_full = ctx.add_typed_action_view(|ctx| {
|
||||
let mut selector = ProfileModelSelector::new(
|
||||
menu_positioning_provider.clone(),
|
||||
@@ -752,7 +782,12 @@ impl AgentInputFooter {
|
||||
},
|
||||
);
|
||||
// Subscribe to AIExecutionProfilesModel to potentially show/hide the profile selector button when profiles are added/removed
|
||||
ctx.subscribe_to_model(&AIExecutionProfilesModel::handle(ctx), |_, _, _, ctx| {
|
||||
ctx.subscribe_to_model(&AIExecutionProfilesModel::handle(ctx), |me, _, _, ctx| {
|
||||
me.update_llm_usage_buttons(ctx);
|
||||
ctx.notify();
|
||||
});
|
||||
ctx.subscribe_to_model(&LLMPreferences::handle(ctx), |me, _, _, ctx| {
|
||||
me.update_llm_usage_buttons(ctx);
|
||||
ctx.notify();
|
||||
});
|
||||
|
||||
@@ -775,6 +810,7 @@ impl AgentInputFooter {
|
||||
| BlocklistAIHistoryEvent::UpdatedAutoexecuteOverride { .. } => {
|
||||
me.sync_fast_forward_button(ctx);
|
||||
me.update_context_window_button(ctx);
|
||||
me.update_llm_usage_buttons(ctx);
|
||||
me.model_selector.update(ctx, |_, ctx| ctx.notify());
|
||||
ctx.notify();
|
||||
}
|
||||
@@ -783,6 +819,7 @@ impl AgentInputFooter {
|
||||
| BlocklistAIHistoryEvent::AppendedExchange { .. }
|
||||
| BlocklistAIHistoryEvent::UpdatedStreamingExchange { .. } => {
|
||||
me.update_context_window_button(ctx);
|
||||
me.update_llm_usage_buttons(ctx);
|
||||
me.model_selector.update(ctx, |_, ctx| ctx.notify());
|
||||
ctx.notify();
|
||||
}
|
||||
@@ -836,6 +873,8 @@ impl AgentInputFooter {
|
||||
plugin_operation_in_progress: false,
|
||||
plugin_chip_ready: false,
|
||||
context_window_button,
|
||||
llm_context_usage_button,
|
||||
llm_cache_details_button,
|
||||
model_selector: profile_model_selector_full,
|
||||
environment_selector,
|
||||
handoff_environment_selector,
|
||||
@@ -860,6 +899,7 @@ impl AgentInputFooter {
|
||||
};
|
||||
me.sync_fast_forward_button(ctx);
|
||||
me.update_context_window_button(ctx);
|
||||
me.update_llm_usage_buttons(ctx);
|
||||
me.update_display_chips(&prompt, ctx);
|
||||
me
|
||||
}
|
||||
@@ -1463,6 +1503,8 @@ impl AgentInputFooter {
|
||||
AgentToolbarItemKind::ModelSelector
|
||||
| AgentToolbarItemKind::NLDToggle
|
||||
| AgentToolbarItemKind::ContextWindowUsage
|
||||
| AgentToolbarItemKind::LLMContextUsage
|
||||
| AgentToolbarItemKind::LLMCacheDetails
|
||||
| AgentToolbarItemKind::FastForwardToggle
|
||||
| AgentToolbarItemKind::HandoffToCloud => None,
|
||||
}
|
||||
@@ -1970,6 +2012,101 @@ impl AgentInputFooter {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_llm_usage_buttons(&self, ctx: &mut ViewContext<Self>) {
|
||||
let metrics = BlocklistAIHistoryModel::as_ref(ctx)
|
||||
.active_conversation(self.terminal_view_id)
|
||||
.map(|conversation| {
|
||||
(
|
||||
conversation.current_context_tokens(),
|
||||
conversation.context_window_usage(),
|
||||
conversation.latest_model_call_cache_read_tokens(),
|
||||
conversation.latest_model_call_cache_write_tokens(),
|
||||
conversation.latest_model_call_cache_miss_tokens(),
|
||||
)
|
||||
});
|
||||
|
||||
let Some((current_context, reported_usage, cache_read, cache_write, cache_miss)) = metrics
|
||||
else {
|
||||
self.llm_context_usage_button.update(ctx, |button, ctx| {
|
||||
button.set_label("Ctx —", ctx);
|
||||
button.set_icon(Some(Icon::ContextRemaining100), ctx);
|
||||
button.set_tooltip(
|
||||
Some("LLM context usage is available after the first model call"),
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
self.llm_cache_details_button.update(ctx, |button, ctx| {
|
||||
button.set_label("Cache —", ctx);
|
||||
button.set_tooltip(
|
||||
Some("LLM cache details are available after the first model call"),
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
let active_model =
|
||||
LLMPreferences::as_ref(ctx).get_active_base_model(ctx, Some(self.terminal_view_id));
|
||||
let profile_context = AIExecutionProfilesModel::as_ref(ctx)
|
||||
.active_profile(Some(self.terminal_view_id), ctx)
|
||||
.data()
|
||||
.context_window_display_value(ctx);
|
||||
let model_max_context = active_model
|
||||
.context_window
|
||||
.default_max
|
||||
.max(active_model.context_window.max);
|
||||
let max_context = profile_context
|
||||
.or((model_max_context > 0).then_some(model_max_context))
|
||||
.unwrap_or(200_000);
|
||||
let context_usage = if max_context > 0 && current_context > 0 {
|
||||
(current_context as f32 / max_context as f32).clamp(0.0, 1.0)
|
||||
} else {
|
||||
reported_usage.clamp(0.0, 1.0)
|
||||
};
|
||||
let context_pct = context_usage * 100.0;
|
||||
let context_label = format!(
|
||||
"Ctx {context_pct:.1}% · {}/{}",
|
||||
format_llm_token_count(current_context),
|
||||
format_llm_token_count(max_context),
|
||||
);
|
||||
let context_tooltip = format!(
|
||||
"Current LLM context: {} of {} tokens ({context_pct:.1}%). This is not cumulative.",
|
||||
format_llm_token_count(current_context),
|
||||
format_llm_token_count(max_context),
|
||||
);
|
||||
let context_icon = icon_for_context_window_usage(context_usage);
|
||||
self.llm_context_usage_button.update(ctx, |button, ctx| {
|
||||
button.set_label(context_label, ctx);
|
||||
button.set_icon(Some(context_icon), ctx);
|
||||
button.set_tooltip(Some(context_tooltip), ctx);
|
||||
});
|
||||
|
||||
let cache_total = cache_read
|
||||
.saturating_add(cache_write)
|
||||
.saturating_add(cache_miss);
|
||||
let cache_hit_pct = if cache_total > 0 {
|
||||
cache_read as f64 / cache_total as f64 * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let cache_label = format!(
|
||||
"Cache {cache_hit_pct:.1}% · R {} · W {} · M {}",
|
||||
format_llm_token_count(cache_read),
|
||||
format_llm_token_count(cache_write),
|
||||
format_llm_token_count(cache_miss),
|
||||
);
|
||||
let cache_tooltip = format!(
|
||||
"Latest model call: {} cache-read, {} cache-write, {} cache-miss tokens.",
|
||||
format_llm_token_count(cache_read),
|
||||
format_llm_token_count(cache_write),
|
||||
format_llm_token_count(cache_miss),
|
||||
);
|
||||
self.llm_cache_details_button.update(ctx, |button, ctx| {
|
||||
button.set_label(cache_label, ctx);
|
||||
button.set_tooltip(Some(cache_tooltip), ctx);
|
||||
});
|
||||
}
|
||||
|
||||
/// Schedules a refresh of the context-window button at the prompt-cache
|
||||
/// expiry instant so the notification dot appears while the conversation is idle.
|
||||
fn reschedule_prompt_cache_expiry_timer(
|
||||
@@ -2101,6 +2238,14 @@ impl AgentInputFooter {
|
||||
stack.finish()
|
||||
})
|
||||
}
|
||||
AgentToolbarItemKind::LLMContextUsage => BlocklistAIHistoryModel::as_ref(app)
|
||||
.active_conversation(self.terminal_view_id)
|
||||
.is_some()
|
||||
.then(|| ChildView::new(&self.llm_context_usage_button).finish()),
|
||||
AgentToolbarItemKind::LLMCacheDetails => BlocklistAIHistoryModel::as_ref(app)
|
||||
.active_conversation(self.terminal_view_id)
|
||||
.is_some()
|
||||
.then(|| ChildView::new(&self.llm_cache_details_button).finish()),
|
||||
AgentToolbarItemKind::ShareSession => {
|
||||
if is_conversation_transcript_context {
|
||||
return None;
|
||||
|
||||
@@ -52,6 +52,8 @@ pub enum AgentToolbarItemKind {
|
||||
ModelSelector,
|
||||
NLDToggle,
|
||||
ContextWindowUsage,
|
||||
LLMContextUsage,
|
||||
LLMCacheDetails,
|
||||
|
||||
// CLI agent only
|
||||
FileExplorer,
|
||||
@@ -83,6 +85,8 @@ impl AgentToolbarItemKind {
|
||||
Self::ModelSelector
|
||||
| Self::NLDToggle
|
||||
| Self::ContextWindowUsage
|
||||
| Self::LLMContextUsage
|
||||
| Self::LLMCacheDetails
|
||||
| Self::FastForwardToggle
|
||||
| Self::HandoffToCloud
|
||||
| Self::ShareSession => ToolbarAvailability::AgentViewOnly,
|
||||
@@ -111,6 +115,8 @@ impl AgentToolbarItemKind {
|
||||
| Self::ModelSelector
|
||||
| Self::NLDToggle
|
||||
| Self::ContextWindowUsage
|
||||
| Self::LLMContextUsage
|
||||
| Self::LLMCacheDetails
|
||||
| Self::RichInput
|
||||
| Self::VoiceInput => true,
|
||||
}
|
||||
@@ -123,7 +129,9 @@ impl AgentToolbarItemKind {
|
||||
Self::NLDToggle => "Autodetection",
|
||||
Self::VoiceInput => "Voice Input",
|
||||
Self::FileAttach => "Attach File",
|
||||
Self::ContextWindowUsage => "Context Usage",
|
||||
Self::ContextWindowUsage => "Warp Context Indicator",
|
||||
Self::LLMContextUsage => "LLM Context Usage",
|
||||
Self::LLMCacheDetails => "LLM Cache Details",
|
||||
Self::FileExplorer => "File Explorer",
|
||||
Self::RichInput => "Rich Input",
|
||||
Self::Settings => "Settings",
|
||||
@@ -141,6 +149,8 @@ impl AgentToolbarItemKind {
|
||||
Self::VoiceInput => Some(Icon::Microphone),
|
||||
Self::FileAttach => Some(Icon::Plus),
|
||||
Self::ContextWindowUsage => Some(Icon::ContextRemaining100),
|
||||
Self::LLMContextUsage => Some(Icon::ContextRemaining100),
|
||||
Self::LLMCacheDetails => Some(Icon::LayersThree01),
|
||||
Self::FileExplorer => Some(Icon::FileCopy),
|
||||
Self::RichInput => Some(Icon::TextInput),
|
||||
Self::Settings => Some(Icon::Settings),
|
||||
@@ -163,6 +173,8 @@ impl AgentToolbarItemKind {
|
||||
Self::ContextChip(_)
|
||||
| Self::NLDToggle
|
||||
| Self::ContextWindowUsage
|
||||
| Self::LLMContextUsage
|
||||
| Self::LLMCacheDetails
|
||||
| Self::FastForwardToggle
|
||||
| Self::HandoffToCloud
|
||||
| Self::ShareSession
|
||||
@@ -214,7 +226,8 @@ impl AgentToolbarItemKind {
|
||||
pub fn default_right() -> Vec<Self> {
|
||||
let mut items = vec![
|
||||
Self::ContextChip(ContextChipKind::AgentPlanAndTodoList),
|
||||
Self::ContextWindowUsage,
|
||||
Self::LLMContextUsage,
|
||||
Self::LLMCacheDetails,
|
||||
Self::ModelSelector,
|
||||
];
|
||||
if FeatureFlag::OzHandoff.is_enabled()
|
||||
@@ -240,6 +253,8 @@ impl AgentToolbarItemKind {
|
||||
Self::VoiceInput,
|
||||
Self::FileAttach,
|
||||
Self::ContextWindowUsage,
|
||||
Self::LLMContextUsage,
|
||||
Self::LLMCacheDetails,
|
||||
]);
|
||||
if FeatureFlag::FastForwardAutoexecuteButton.is_enabled() {
|
||||
items.push(Self::FastForwardToggle);
|
||||
|
||||
@@ -81,11 +81,7 @@ pub(crate) fn conversation_id_for_agent_id_in_orchestration(
|
||||
history,
|
||||
orchestrator_id,
|
||||
))
|
||||
.find(|conversation_id| {
|
||||
history
|
||||
.conversation(conversation_id)
|
||||
.is_some_and(|conversation| matches(conversation))
|
||||
})
|
||||
.find(|conversation_id| history.conversation(conversation_id).is_some_and(&matches))
|
||||
.or_else(|| conversation_id_for_agent_id(canonical_id, app))
|
||||
}
|
||||
|
||||
|
||||
@@ -1013,6 +1013,11 @@ impl BlocklistAIStatusBar {
|
||||
.or(self.warping_message)
|
||||
.unwrap_or_else(|| random_load_output_message())
|
||||
.to_owned();
|
||||
let retry_status_text = self
|
||||
.controller
|
||||
.as_ref(app)
|
||||
.provider_retry_status(conversation.id())
|
||||
.map(|status| status.label());
|
||||
let secondary_element = if fallback_warping_text.is_some() {
|
||||
Some(render_fallback_explanation(model.as_ref(), app))
|
||||
} else {
|
||||
@@ -1071,6 +1076,7 @@ impl BlocklistAIStatusBar {
|
||||
)),
|
||||
force_refresh_button,
|
||||
default_warping_text,
|
||||
retry_status_text,
|
||||
secondary_element,
|
||||
last_snapshot_at,
|
||||
warping_start_time: self.warping_start_time,
|
||||
|
||||
@@ -218,6 +218,7 @@ pub struct WarpingProps<'a, V> {
|
||||
pub action_model: &'a BlocklistAIActionModel,
|
||||
pub terminal_model: &'a TerminalModel,
|
||||
pub default_warping_text: String,
|
||||
pub retry_status_text: Option<String>,
|
||||
pub secondary_element: Option<Box<dyn Element>>,
|
||||
/// When an LRC subagent has sent at least one snapshot, the timestamp of the most recent snapshot.
|
||||
pub last_snapshot_at: Option<instant::Instant>,
|
||||
@@ -337,14 +338,6 @@ pub fn render_warping_indicator<V: View>(
|
||||
|
||||
let mut should_render_waiting_icon = false;
|
||||
let mut non_shimmering_text = None;
|
||||
if let Some(status_message) = props
|
||||
.model
|
||||
.conversation(app)
|
||||
.and_then(|conversation| conversation.status_error_message())
|
||||
.filter(|message| message.starts_with("Retrying LLM request"))
|
||||
{
|
||||
non_shimmering_text = Some(format!(" • {status_message}"));
|
||||
}
|
||||
let message = if let Some(summarization_type) = summarization_type {
|
||||
// Choose the appropriate message based on summarization type
|
||||
let base_message = match summarization_type {
|
||||
@@ -486,6 +479,13 @@ pub fn render_warping_indicator<V: View>(
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(retry_status_text) = props.retry_status_text.as_deref() {
|
||||
non_shimmering_text = Some(match non_shimmering_text {
|
||||
Some(text) if !text.is_empty() => format!("{text} {retry_status_text}"),
|
||||
Some(_) | None => format!(" {retry_status_text}"),
|
||||
});
|
||||
}
|
||||
|
||||
let appearance = Appearance::as_ref(app);
|
||||
|
||||
let mut buttons_row = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
+521
-272
File diff suppressed because it is too large
Load Diff
@@ -75,6 +75,7 @@ fn provider_lifecycle_logs_expose_llm_completion_and_sanitize_errors() {
|
||||
runtime_id: "rig:openai".to_owned(),
|
||||
model_id: "test-model".to_owned(),
|
||||
retry_attempt: 1,
|
||||
max_retries: 3,
|
||||
elapsed_ms: 120_000,
|
||||
error: retry_error,
|
||||
},
|
||||
@@ -112,6 +113,7 @@ fn provider_lifecycle_logs_expose_llm_completion_and_sanitize_errors() {
|
||||
assert_eq!(records[0].context["llm_finished"], false);
|
||||
assert_eq!(records[1].context["llm_finished"], false);
|
||||
assert_eq!(records[2].context["llm_finished"], false);
|
||||
assert_eq!(records[2].context["max_retries"], 3);
|
||||
assert_eq!(records[3].context["llm_finished"], true);
|
||||
assert_eq!(records[2].level, RemoteLogLevel::Warn);
|
||||
assert_eq!(records[2].context["error"], "[redacted] connection failed");
|
||||
@@ -124,6 +126,16 @@ fn provider_lifecycle_logs_expose_llm_completion_and_sanitize_errors() {
|
||||
assert_eq!(records[3].context["tool_call_count"], 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_retry_status_uses_configured_budget_in_label() {
|
||||
let status = super::ProviderRetryStatus {
|
||||
attempt: 1,
|
||||
max_retries: 5,
|
||||
};
|
||||
|
||||
assert_eq!(status.label(), "(Retry 1/5)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_terminal_logs_distinguish_clean_completion_from_failure() {
|
||||
let conversation_id = AIConversationId::new();
|
||||
@@ -455,6 +467,7 @@ fn queued_provider_follow_up_persists_while_active_slot_is_unprepared() {
|
||||
pending_monitor_observation: None,
|
||||
pending_command_completion: None,
|
||||
monitor_prose_continuations: 0,
|
||||
retry_status: None,
|
||||
},
|
||||
);
|
||||
controller
|
||||
@@ -480,6 +493,7 @@ fn queued_provider_follow_up_persists_while_active_slot_is_unprepared() {
|
||||
pending_monitor_observation: None,
|
||||
pending_command_completion: None,
|
||||
monitor_prose_continuations: 0,
|
||||
retry_status: None,
|
||||
},
|
||||
base_provider_config: crate::ai::provider::ProviderConfig::None,
|
||||
cli_provider_config: crate::ai::provider::ProviderConfig::None,
|
||||
@@ -1029,6 +1043,7 @@ fn cancelling_provider_startup_keeps_slot_and_durable_cancellation() {
|
||||
pending_monitor_observation: None,
|
||||
pending_command_completion: None,
|
||||
monitor_prose_continuations: 0,
|
||||
retry_status: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1127,6 +1142,7 @@ fn same_conversation_follow_up_waits_for_cancelled_provider_generation_cleanup()
|
||||
pending_monitor_observation: None,
|
||||
pending_command_completion: None,
|
||||
monitor_prose_continuations: 0,
|
||||
retry_status: None,
|
||||
},
|
||||
);
|
||||
controller
|
||||
@@ -1158,6 +1174,7 @@ fn same_conversation_follow_up_waits_for_cancelled_provider_generation_cleanup()
|
||||
pending_monitor_observation: None,
|
||||
pending_command_completion: None,
|
||||
monitor_prose_continuations: 0,
|
||||
retry_status: None,
|
||||
},
|
||||
base_provider_config: crate::ai::provider::ProviderConfig::None,
|
||||
cli_provider_config: crate::ai::provider::ProviderConfig::None,
|
||||
@@ -1267,6 +1284,7 @@ fn non_follow_up_provider_cancellation_does_not_admit_an_overlapping_generation(
|
||||
pending_monitor_observation: None,
|
||||
pending_command_completion: None,
|
||||
monitor_prose_continuations: 0,
|
||||
retry_status: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2033,18 +2051,9 @@ fn restored_evidence_is_ignored_without_a_command_monitor() {
|
||||
|
||||
#[test]
|
||||
fn restored_projection_accepts_empty_or_complete_and_rejects_partial_state() {
|
||||
assert_eq!(
|
||||
super::restored_projection_was_initialized(false, false, false).unwrap(),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
super::restored_projection_was_initialized(true, true, false).unwrap(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
super::restored_projection_was_initialized(true, true, true).unwrap(),
|
||||
true
|
||||
);
|
||||
assert!(!super::restored_projection_was_initialized(false, false, false).unwrap());
|
||||
assert!(super::restored_projection_was_initialized(true, true, false).unwrap());
|
||||
assert!(super::restored_projection_was_initialized(true, true, true).unwrap());
|
||||
for state in [
|
||||
(false, false, true),
|
||||
(false, true, false),
|
||||
|
||||
Reference in New Issue
Block a user