Skip to main content

bobby_browser_client/
state.rs

1//! Session and page runtime state returned by `/v1` endpoints.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::{ExecutionPolicy, PageId, SessionId};
7
8/// Page rendering / interaction mode.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
11pub enum PageMode {
12    Document,
13    Interactive,
14    Render,
15}
16
17/// `GET /v1/runtime` body.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
20pub struct RuntimeInfo {
21    pub version: String,
22    pub capabilities: Vec<String>,
23    pub active_sessions: usize,
24    pub queued_jobs: usize,
25    pub uptime_ms: u64,
26    /// Operator-set health budget for one vision propose round-trip
27    /// (`[vision].propose_budget_ms`); absent when no budget is configured.
28    #[serde(
29        default,
30        rename = "visionProposeBudgetMs",
31        skip_serializing_if = "Option::is_none"
32    )]
33    pub vision_propose_budget_ms: Option<u64>,
34    #[serde(
35        default,
36        rename = "operationalMetrics",
37        skip_serializing_if = "Option::is_none"
38    )]
39    pub operational_metrics: Option<OperationalMetricsSnapshot>,
40    /// Per-provider health derived from recorded vision proposal outcomes and
41    /// the configured budgets (`[vision].propose_budget_ms`,
42    /// `[vision].health_failure_threshold`). Report-only. Absent when no
43    /// vision provider is configured.
44    #[serde(
45        default,
46        rename = "providerHealth",
47        skip_serializing_if = "Option::is_none"
48    )]
49    pub provider_health: Option<Vec<ProviderHealthSnapshot>>,
50}
51
52/// Health classification of one vision provider boundary. `degraded` means
53/// consecutive propose-budget violations reached the failure threshold;
54/// `unhealthy` means consecutive provider failures did.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
57#[serde(rename_all = "camelCase")]
58pub enum ProviderHealthStatus {
59    Healthy,
60    Degraded,
61    Unhealthy,
62}
63
64/// Operator-facing health of one vision provider mode. Carries the thresholds
65/// that produced `status` so consumers never re-derive them.
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
67#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
68#[serde(rename_all = "camelCase")]
69pub struct ProviderHealthSnapshot {
70    pub provider_mode: String,
71    pub status: ProviderHealthStatus,
72    pub successes: u64,
73    pub failures: u64,
74    pub consecutive_failures: u64,
75    pub budget_violations: u64,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub last_latency_ms: Option<u64>,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub latency_budget_ms: Option<u64>,
80    pub failure_threshold: u64,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
84#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
85#[serde(rename_all = "camelCase")]
86pub struct OperationalMetricsSnapshot {
87    pub observation_window_ms: u64,
88    pub intent: IntentMetricsSnapshot,
89    pub context: ContextMetricsSnapshot,
90    #[serde(default)]
91    pub context_ranked_vision: ContextRankedVisionMetricsSnapshot,
92    pub prefill: PrefillMetricsSnapshot,
93    pub vision: VisionMetricsSnapshot,
94    pub verification: VerificationMetricsSnapshot,
95    pub retries: RetryMetricsSnapshot,
96    pub reconciliation: ReconciliationMetricsSnapshot,
97    pub workflow_calls: WorkflowCallMetricsSnapshot,
98}
99
100macro_rules! metric_snapshot {
101    ($name:ident { $($field:ident),+ $(,)? }) => {
102        #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
103        #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
104        #[serde(rename_all = "camelCase")]
105        pub struct $name { $(pub $field: u64,)+ }
106    };
107}
108
109metric_snapshot!(IntentMetricsSnapshot {
110    total,
111    locate,
112    fill,
113    complete_form,
114    extract,
115    submit,
116    wait_for_state,
117    follow,
118    dismiss,
119    solve_challenge,
120    detect_challenge,
121    deterministic,
122    context,
123    vision_prefill,
124    vision_fallback,
125});
126metric_snapshot!(ContextMetricsSnapshot {
127    hit,
128    miss,
129    ambiguous_refusal,
130    stale_rejection,
131    error,
132});
133metric_snapshot!(PrefillMetricsSnapshot {
134    hit,
135    miss,
136    dropped_entry,
137    policy_denied,
138    provider_failure,
139});
140metric_snapshot!(ConfidenceMetricsSnapshot {
141    below_acceptance,
142    accepted,
143    high,
144    unreported,
145});
146metric_snapshot!(VerificationMetricsSnapshot {
147    accepted,
148    target_not_found,
149    target_ambiguous,
150    obstruction_persisted,
151    value_mismatch,
152    other_rejected,
153});
154metric_snapshot!(RetryMetricsSnapshot {
155    transport,
156    timeout,
157    target_detached,
158    state_conflict,
159    other,
160});
161metric_snapshot!(ReconciliationMetricsSnapshot {
162    resumed,
163    restarted,
164    needs_reconciliation,
165    failed,
166});
167metric_snapshot!(WorkflowCallMetricsSnapshot {
168    lifecycle,
169    discovery,
170    read,
171    mutation,
172    composite_workflow,
173    recovery,
174    artifact,
175    job,
176});
177
178#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
179#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
180#[serde(rename_all = "camelCase")]
181pub struct VisionMetricsSnapshot {
182    pub attempted: u64,
183    pub accepted: u64,
184    pub rejected: u64,
185    pub abstained: u64,
186    pub timed_out: u64,
187    pub failed: u64,
188    pub provider_http: u64,
189    pub provider_acp: u64,
190    pub provider_direct_local: u64,
191    pub latency_ms: LatencyHistogramSnapshot,
192    pub confidence: ConfidenceMetricsSnapshot,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
197#[serde(rename_all = "camelCase")]
198pub struct LatencyHistogramSnapshot {
199    pub buckets: Vec<LatencyBucketSnapshot>,
200    pub overflow: u64,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
204#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
205#[serde(rename_all = "camelCase")]
206pub struct LatencyBucketSnapshot {
207    pub upper_bound_ms: u64,
208    pub count: u64,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
212#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
213#[serde(rename_all = "camelCase")]
214pub struct ContextRankedVisionMetricsSnapshot {
215    pub attempted: u64,
216    pub source_observed: u64,
217    pub source_vision_promoted: u64,
218    pub source_unreported: u64,
219    pub hit: u64,
220    pub miss: u64,
221    pub ambiguous_refusal: u64,
222    pub stale_rejection: u64,
223    pub error: u64,
224    pub provider_escalations: u64,
225    pub provider_http: u64,
226    pub provider_acp: u64,
227    pub provider_direct_local: u64,
228    pub candidate_ranking_latency_ms: LatencyHistogramSnapshot,
229    pub confidence: ConfidenceMetricsSnapshot,
230    pub verification_accepted: u64,
231    pub verification_rejected: u64,
232}
233
234impl Default for ContextRankedVisionMetricsSnapshot {
235    fn default() -> Self {
236        Self {
237            attempted: 0,
238            source_observed: 0,
239            source_vision_promoted: 0,
240            source_unreported: 0,
241            hit: 0,
242            miss: 0,
243            ambiguous_refusal: 0,
244            stale_rejection: 0,
245            error: 0,
246            provider_escalations: 0,
247            provider_http: 0,
248            provider_acp: 0,
249            provider_direct_local: 0,
250            candidate_ranking_latency_ms: LatencyHistogramSnapshot {
251                buckets: Vec::new(),
252                overflow: 0,
253            },
254            confidence: ConfidenceMetricsSnapshot {
255                below_acceptance: 0,
256                accepted: 0,
257                high: 0,
258                unreported: 0,
259            },
260            verification_accepted: 0,
261            verification_rejected: 0,
262        }
263    }
264}
265
266/// Browser session returned by session create/list endpoints.
267#[derive(Debug, Clone, Serialize, Deserialize)]
268#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
269pub struct SessionState {
270    pub id: SessionId,
271    pub profile: String,
272    pub proxy: Option<String>,
273    pub page_ids: Vec<PageId>,
274    pub created_at: DateTime<Utc>,
275    pub last_used_at: DateTime<Utc>,
276    pub execution_policy: ExecutionPolicy,
277    /// Set at creation by `CreateSessionRequest::zigzagzig`: page-bound
278    /// commands run under the ZigZagZig recovery ladder. Recorded
279    /// separately from the policy — a hand-assembled everything-on policy
280    /// does not opt into the ladder. Absent from the wire when off, so
281    /// plain sessions carry no godmode baggage.
282    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
283    pub zigzagzig: bool,
284}
285
286/// Page within a session.
287#[derive(Debug, Clone, Serialize, Deserialize)]
288#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
289pub struct PageState {
290    pub id: PageId,
291    pub session_id: SessionId,
292    pub url: Option<String>,
293    pub mode: PageMode,
294    pub ready_state: String,
295    pub pending_requests: usize,
296}
297
298/// Result of a navigation command.
299#[derive(Debug, Clone, Serialize, Deserialize)]
300#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
301pub struct NavigationResult {
302    pub page_id: PageId,
303    pub url: String,
304    pub ready_state: String,
305}
306
307/// Result of a structured extract command.
308#[derive(Debug, Clone, Serialize, Deserialize)]
309#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
310pub struct ExtractResult {
311    pub page_id: PageId,
312    pub data: serde_json::Value,
313}