magi-code 0.63.4

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;

pub(crate) const UI_DRAIN_BUDGET: DrainBudget = DrainBudget {
    max_events: 128,
    max_elapsed: Duration::from_millis(2),
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DrainBudget {
    pub(crate) max_events: usize,
    pub(crate) max_elapsed: Duration,
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct DrainOutcome {
    pub(crate) result: DrainResult,
    pub(crate) processed: usize,
    pub(crate) exhausted: bool,
}

#[derive(Debug, Default, Clone)]
pub(crate) struct DrainResult {
    pub(crate) changed: bool,
    pub(crate) run_finished: bool,
    pub(crate) model_catalog_finished: bool,
    pub(crate) model_catalog_loaded: Vec<(
        u64,
        Result<crate::model_catalog::CatalogForUi, String>,
        usize,
    )>,
    pub(crate) usage_loaded: Vec<(u64, crate::tui::usage::UsageLoadResult)>,
    pub(crate) startup_loaded: Vec<(u64, Result<TuiStartupLoaded, String>)>,
    pub(crate) session_preview_loaded: Vec<(u64, String, state::SessionPreviewState)>,
    pub(crate) session_switch_loaded: Vec<(u64, String, Result<SessionSwitchLoad, String>)>,
    pub(crate) footer_git_branch_loaded: Vec<(u64, Option<String>)>,
}

impl PartialEq for DrainResult {
    fn eq(&self, other: &Self) -> bool {
        self.changed == other.changed
            && self.run_finished == other.run_finished
            && self.model_catalog_finished == other.model_catalog_finished
            && self.model_catalog_loaded.len() == other.model_catalog_loaded.len()
            && self.usage_loaded == other.usage_loaded
            && self.startup_loaded == other.startup_loaded
            && self.session_preview_loaded == other.session_preview_loaded
            && self.session_switch_loaded.len() == other.session_switch_loaded.len()
            && self
                .session_switch_loaded
                .iter()
                .zip(&other.session_switch_loaded)
                .all(|(left, right)| {
                    left.0 == right.0 && left.1 == right.1 && left.2.is_ok() == right.2.is_ok()
                })
            && self.footer_git_branch_loaded == other.footer_git_branch_loaded
    }
}

impl Eq for DrainResult {}

pub(crate) fn drain_tui_events(
    receiver: &Receiver<TuiEvent>,
    state: &mut state::MissionControlState,
) -> DrainResult {
    let mut result = DrainResult::default();
    while let Ok(event) = receiver.try_recv() {
        apply_tui_event_to_state(state, event, &mut result);
    }
    result
}

pub(crate) fn drain_tui_events_budgeted(
    receiver: &Receiver<TuiEvent>,
    state: &mut state::MissionControlState,
    budget: DrainBudget,
) -> DrainOutcome {
    drain_tui_events_budgeted_with_clock(receiver, state, budget, Instant::now)
}

pub(crate) fn drain_tui_events_budgeted_with_clock(
    receiver: &Receiver<TuiEvent>,
    state: &mut state::MissionControlState,
    budget: DrainBudget,
    mut now: impl FnMut() -> Instant,
) -> DrainOutcome {
    let started = now();
    drain_tui_events_budgeted_from(receiver, state, budget, 0, started, now)
}

pub(crate) fn drain_tui_events_budgeted_from(
    receiver: &Receiver<TuiEvent>,
    state: &mut state::MissionControlState,
    budget: DrainBudget,
    already_processed: usize,
    started: Instant,
    mut now: impl FnMut() -> Instant,
) -> DrainOutcome {
    let mut outcome = DrainOutcome {
        processed: already_processed,
        ..Default::default()
    };
    if outcome.processed >= budget.max_events {
        outcome.exhausted = true;
        return outcome;
    }
    while outcome.processed < budget.max_events {
        if outcome.processed > 0 && now().duration_since(started) >= budget.max_elapsed {
            outcome.exhausted = true;
            return outcome;
        }
        let Ok(event) = receiver.try_recv() else {
            return outcome;
        };
        apply_tui_event_to_state(state, event, &mut outcome.result);
        outcome.processed += 1;
    }
    outcome.exhausted = true;
    outcome
}

pub(crate) fn apply_tui_event_to_state(
    state: &mut state::MissionControlState,
    event: TuiEvent,
    result: &mut DrainResult,
) {
    match event {
        TuiEvent::Output(event) => apply_output_event_to_state(state, event, result),
        TuiEvent::Activity(event) => apply_activity_event_to_state(state, event, result),
        event => apply_control_event_to_state(state, event, result),
    }
}

fn apply_output_event_to_state(
    state: &mut state::MissionControlState,
    event: OutputEvent,
    result: &mut DrainResult,
) {
    state.apply_output_event(&event);
    result.changed = true;
}

pub(crate) fn apply_activity_event_to_state(
    state: &mut state::MissionControlState,
    event: ActivityEvent,
    result: &mut DrainResult,
) {
    state.apply_activity_event(event);
    result.changed = true;
}

pub(crate) fn apply_control_event_to_state(
    state: &mut state::MissionControlState,
    event: TuiEvent,
    result: &mut DrainResult,
) {
    match event {
        TuiEvent::Error(error) => {
            state.finish_assistant_streaming();
            state.record_error_transcript(&error);
            state.status = error;
        }
        TuiEvent::ModelCatalog {
            request_id,
            result: catalog_result,
            visible_rows,
        } => {
            if state.pending_model_catalog_request_id != Some(request_id) {
                return;
            }
            state.pending_model_catalog_request_id = None;
            result.model_catalog_finished = true;
            result
                .model_catalog_loaded
                .push((request_id, catalog_result, visible_rows));
        }
        TuiEvent::UsageLoaded {
            request_id,
            result: usage_result,
        } => {
            if state.pending_usage_request_id != Some(request_id) || !state.usage_modal_visible() {
                return;
            }
            state.pending_usage_request_id = None;
            let status = match &usage_result {
                crate::tui::usage::UsageLoadResult::Loaded(report)
                    if report.providers.is_empty() =>
                {
                    "no provider usage data available"
                }
                crate::tui::usage::UsageLoadResult::Loaded(_) => "provider usage loaded",
                crate::tui::usage::UsageLoadResult::MissingCli => "quota-axi is not installed",
                crate::tui::usage::UsageLoadResult::Error(_) => "failed to load provider usage",
            };
            state.set_usage_modal_result(usage_result.clone());
            state.status = status.to_string();
            result.usage_loaded.push((request_id, usage_result));
        }
        TuiEvent::LoginInstructions(instructions) => {
            state.set_login_instructions(instructions.url, instructions.message);
            state.status =
                "OpenAI Codex login waiting for browser callback or pasted redirect".to_string();
        }
        TuiEvent::CompactionFinishedAuthoritative {
            result: outcome,
            activity,
        } => apply_worker_final_event(
            state,
            WorkerFinalEvent::CompactionFinished {
                result: outcome,
                activity,
            },
        ),
        TuiEvent::LoginFinished {
            provider_id,
            result: outcome,
        } => match outcome {
            Ok(message) => {
                state.clear_active_login();
                if state.provider == provider_id {
                    state.provider_ready = true;
                }
                state.status = message;
            }
            Err(error) => {
                state.clear_active_login();
                state.record_error_transcript(&error);
                state.status = error;
            }
        },
        TuiEvent::Done => {
            state.finish_assistant_streaming();
            state.status = "run complete".to_string();
        }
        TuiEvent::RunCanceled { prompt } => {
            state.finish_assistant_streaming();
            let preview = crate::tui::transcript::sanitize_preview(&prompt);
            state.mark_running_prompt_canceled();
            state.record_canceled_transcript(&preview);
            state.status = format!("canceled: {preview}");
        }
        TuiEvent::RunFinished { final_event } => {
            if let Some(event) = final_event {
                apply_control_event_to_state(state, *event, result);
            }
            state.finish_assistant_streaming();
            state.clear_running_prompt();
            result.run_finished = true;
        }
        TuiEvent::SessionTitleUpdated { session_id, title } => {
            state.apply_session_title_update(&session_id, &title);
        }
        TuiEvent::StartupLoaded {
            request_id,
            result: loaded,
        } => {
            if state.pending_startup_request_id != Some(request_id) {
                return;
            }
            state.pending_startup_request_id = None;
            match *loaded {
                Ok(loaded) => result.startup_loaded.push((request_id, Ok(loaded))),
                Err(error) => {
                    state.record_error_transcript(&error);
                    state.status = error;
                }
            }
        }
        TuiEvent::SessionPreviewLoaded {
            request_id,
            session_id,
            preview,
        } => {
            result
                .session_preview_loaded
                .push((request_id, session_id, preview));
        }
        TuiEvent::SessionSwitchLoaded {
            request_id,
            session_id,
            result: switch_result,
        } => {
            result
                .session_switch_loaded
                .push((request_id, session_id, *switch_result));
        }
        TuiEvent::FooterGitBranchLoaded { request_id, branch } => {
            result.footer_git_branch_loaded.push((request_id, branch));
        }
        TuiEvent::Output(_) | TuiEvent::Activity(_) => {}
    }
    result.changed = true;
}

pub(crate) fn drain_stale_tui_events(receiver: &Receiver<TuiEvent>) -> DrainResult {
    let mut result = DrainResult::default();
    while let Ok(event) = receiver.try_recv() {
        if matches!(event, TuiEvent::RunFinished { .. }) {
            result.run_finished = true;
        }
        result.changed = true;
    }
    result
}

pub(crate) fn picker_visible_rows(list_area: Option<ratatui::layout::Rect>) -> usize {
    list_area
        .map(|area| area.height.max(1) as usize)
        .unwrap_or(input::DEFAULT_PICKER_VISIBLE_ROWS as usize)
}