Skip to main content

vtcode_commons/ui_protocol/
progress.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::Instant;
3
4static NEXT_OPERATION: AtomicU64 = AtomicU64::new(1);
5
6/// UI-only identity and monotonic origin. Never persisted or sent to a provider.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct ProgressOperation {
9    id: u64,
10    started_at: Instant,
11}
12
13impl ProgressOperation {
14    pub fn start() -> Self {
15        Self {
16            id: NEXT_OPERATION.fetch_add(1, Ordering::Relaxed),
17            started_at: Instant::now(),
18        }
19    }
20
21    pub fn id(self) -> u64 {
22        self.id
23    }
24
25    pub fn started_at(self) -> Instant {
26        self.started_at
27    }
28}
29
30/// Actual work phases; these labels never grant admission or change input ownership.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum ProgressPhase {
33    Initializing,
34    PreparingContext,
35    SavingCheckpoint,
36    WaitingForModel,
37    Processing,
38    ReceivingResponse,
39    Retrying,
40    CheckingPermissions,
41    WaitingForApproval,
42    RunningTools,
43    CheckingToolOutput,
44}
45
46impl ProgressPhase {
47    pub fn label(self) -> &'static str {
48        match self {
49            Self::Initializing => "Initializing session",
50            Self::PreparingContext => "Preparing context",
51            Self::SavingCheckpoint => "Saving checkpoint",
52            Self::WaitingForModel => "Waiting for model",
53            Self::Processing => "Processing",
54            Self::ReceivingResponse => "Receiving response",
55            Self::Retrying => "Retrying request",
56            Self::CheckingPermissions => "Checking permissions",
57            Self::WaitingForApproval => "Waiting for approval",
58            Self::RunningTools => "Running tools",
59            Self::CheckingToolOutput => "Checking tool output...",
60        }
61    }
62
63    pub fn is_animated(self) -> bool {
64        self != Self::WaitingForApproval
65    }
66
67    pub fn format(self, elapsed_secs: u64) -> String {
68        if elapsed_secs == 0 {
69            self.label().to_string()
70        } else {
71            format!("{} ยท {elapsed_secs}s", self.label())
72        }
73    }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum ProgressUpdate {
78    Begin {
79        operation: ProgressOperation,
80        phase: ProgressPhase,
81    },
82    Phase {
83        operation: ProgressOperation,
84        phase: ProgressPhase,
85    },
86    Finish {
87        operation: ProgressOperation,
88    },
89}