1use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6use crate::{AttemptId, CommandId, PageId};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
10#[serde(rename_all = "camelCase", deny_unknown_fields)]
11pub struct ControlActionEvidence {
12 pub operation: crate::FormControlOperation,
13 pub target: crate::FormControlTarget,
14 pub state: crate::FormControlState,
15 pub validity: crate::FormControlValidity,
16 pub node_replaced: bool,
17 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub revealed_controls: Vec<crate::forms::RevealedControl>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
27#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
28#[serde(rename_all = "camelCase", deny_unknown_fields)]
29pub struct AccessibilityTarget {
30 pub role: String,
31 pub accessible_name: String,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub ordinal: Option<usize>,
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
39 pub frame_path: Vec<crate::forms::SemanticTargetSegment>,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
48#[serde(rename_all = "camelCase")]
49pub struct ContextAnswer {
50 pub target: AccessibilityTarget,
51 pub confidence: f32,
52 #[serde(default)]
56 pub observed_at: ContextObservedAt,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub source: Option<ContextAnswerSource>,
61}
62
63impl ContextAnswer {
64 pub fn page_derived(&self) -> bool {
66 true
67 }
68}
69
70impl Default for ContextObservedAt {
71 fn default() -> Self {
72 Self::Generation { generation: 0 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
79#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
80#[serde(tag = "kind", rename_all = "camelCase")]
81pub enum ContextObservedAt {
82 Generation { generation: u64 },
83 Persisted,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
89#[serde(rename_all = "kebab-case")]
90pub enum ContextAnswerSource {
91 Observed,
92 VisionPromoted,
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
100#[serde(rename_all = "camelCase")]
101pub struct ContextNeighbors {
102 pub answer: ContextAnswer,
103 pub form: String,
105 pub page_pattern: String,
107 pub controls: Vec<ContextNeighborControl>,
108}
109
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
112#[serde(rename_all = "camelCase")]
113pub struct ContextNeighborControl {
114 pub role: String,
115 pub accessible_name: String,
116 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub ordinal: Option<usize>,
118 pub intents: std::collections::BTreeMap<String, ContextNeighborStats>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
124#[serde(rename_all = "camelCase")]
125pub struct ContextNeighborStats {
126 pub success_count: u64,
127 pub failure_count: u64,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub last_verified_day: Option<u32>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub source: Option<ContextAnswerSource>,
133}
134
135#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
138#[serde(rename_all = "camelCase")]
139pub struct ContextSiteView {
140 pub site_key: String,
141 pub pages: std::collections::BTreeMap<
142 String,
143 std::collections::BTreeMap<String, Vec<ContextNeighborControl>>,
144 >,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
149#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
150pub enum ContextMissReason {
151 #[serde(rename = "notRemembered")]
152 NotRemembered,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
157#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
158pub enum ContextNextStep {
159 #[serde(rename = "a11y_snapshot")]
160 A11ySnapshot,
161}
162
163#[derive(Debug, Clone, PartialEq, Serialize)]
165#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
166#[serde(rename_all = "camelCase", deny_unknown_fields)]
167pub struct ContextAskResponse {
168 pub answer: Option<ContextAnswer>,
169 pub hit: bool,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub reason: Option<ContextMissReason>,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub next_step: Option<ContextNextStep>,
174}
175
176#[derive(Deserialize)]
177#[serde(rename_all = "camelCase", deny_unknown_fields)]
178struct ContextAskResponseWire {
179 answer: Option<ContextAnswer>,
180 hit: bool,
181 #[serde(default)]
182 reason: Option<ContextMissReason>,
183 #[serde(default)]
184 next_step: Option<ContextNextStep>,
185 #[serde(default)]
186 page_derived: Option<bool>,
187}
188
189impl<'de> Deserialize<'de> for ContextAskResponse {
190 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
191 let wire = ContextAskResponseWire::deserialize(deserializer)?;
192 if wire.page_derived == Some(false) {
193 return Err(serde::de::Error::custom("pageDerived must be true"));
194 }
195 Ok(Self {
196 answer: wire.answer,
197 hit: wire.hit,
198 reason: wire.reason,
199 next_step: wire.next_step,
200 })
201 }
202}
203
204impl ContextAskResponse {
205 pub fn page_derived(&self) -> bool {
206 true
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Serialize)]
212#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
213#[serde(rename_all = "camelCase", deny_unknown_fields)]
214pub struct ContextNeighborsResponse {
215 pub neighbors: Option<ContextNeighbors>,
216 pub hit: bool,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub reason: Option<ContextMissReason>,
219 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub next_step: Option<ContextNextStep>,
221}
222
223#[derive(Deserialize)]
224#[serde(rename_all = "camelCase", deny_unknown_fields)]
225struct ContextNeighborsResponseWire {
226 neighbors: Option<ContextNeighbors>,
227 hit: bool,
228 #[serde(default)]
229 reason: Option<ContextMissReason>,
230 #[serde(default)]
231 next_step: Option<ContextNextStep>,
232 #[serde(default)]
233 page_derived: Option<bool>,
234}
235
236impl<'de> Deserialize<'de> for ContextNeighborsResponse {
237 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
238 let wire = ContextNeighborsResponseWire::deserialize(deserializer)?;
239 if wire.page_derived == Some(false) {
240 return Err(serde::de::Error::custom("pageDerived must be true"));
241 }
242 Ok(Self {
243 neighbors: wire.neighbors,
244 hit: wire.hit,
245 reason: wire.reason,
246 next_step: wire.next_step,
247 })
248 }
249}
250
251impl ContextNeighborsResponse {
252 pub fn page_derived(&self) -> bool {
253 true
254 }
255}
256
257#[derive(Debug, Clone, PartialEq, Serialize)]
259#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
260#[serde(rename_all = "camelCase", deny_unknown_fields)]
261pub struct ContextSiteResponse {
262 pub site: Option<ContextSiteView>,
263}
264
265#[derive(Deserialize)]
266#[serde(rename_all = "camelCase", deny_unknown_fields)]
267struct ContextSiteResponseWire {
268 site: Option<ContextSiteView>,
269 #[serde(default)]
270 page_derived: Option<bool>,
271}
272
273impl<'de> Deserialize<'de> for ContextSiteResponse {
274 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
275 let wire = ContextSiteResponseWire::deserialize(deserializer)?;
276 if wire.page_derived == Some(false) {
277 return Err(serde::de::Error::custom("pageDerived must be true"));
278 }
279 Ok(Self { site: wire.site })
280 }
281}
282
283impl ContextSiteResponse {
284 pub fn page_derived(&self) -> bool {
285 true
286 }
287}
288
289#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
291#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
292#[serde(rename_all = "camelCase", deny_unknown_fields)]
293pub struct AccessibilityNode {
294 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub role: Option<String>,
296 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub name: Option<String>,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub target: Option<AccessibilityTarget>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub value: Option<String>,
302 #[serde(default, skip_serializing_if = "Option::is_none")]
303 pub description: Option<String>,
304 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub required: Option<bool>,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub disabled: Option<bool>,
308 #[serde(default, skip_serializing_if = "Option::is_none")]
309 pub read_only: Option<bool>,
310 #[serde(default, skip_serializing_if = "Option::is_none")]
311 pub invalid: Option<bool>,
312 #[serde(default, skip_serializing_if = "Option::is_none")]
313 pub checked: Option<bool>,
314 #[serde(default, skip_serializing_if = "Option::is_none")]
315 pub autocomplete: Option<String>,
316 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub url: Option<String>,
320 #[serde(default, skip_serializing_if = "Option::is_none")]
321 pub value_min: Option<String>,
322 #[serde(default, skip_serializing_if = "Option::is_none")]
323 pub value_max: Option<String>,
324 #[serde(default, skip_serializing_if = "Vec::is_empty")]
325 pub children: Vec<AccessibilityNode>,
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
329#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
330#[serde(
331 tag = "status",
332 rename_all = "camelCase",
333 rename_all_fields = "camelCase"
334)]
335pub enum CommandOutcome {
336 Completed {
337 command_id: CommandId,
338 evidence: Vec<Evidence>,
339 },
340 RetryableFailure {
341 command_id: CommandId,
342 error: CommandError,
343 },
344 NeedsReconciliation {
345 command_id: CommandId,
346 error: CommandError,
347 evidence: Vec<Evidence>,
348 },
349 PolicyDenied {
350 command_id: CommandId,
351 error: CommandError,
352 },
353 ResourceExhausted {
354 command_id: CommandId,
355 error: CommandError,
356 retry_after_ms: u64,
357 },
358 Restarted {
359 command_id: CommandId,
360 prior_attempt_id: AttemptId,
361 attempt_id: AttemptId,
362 reason: String,
363 #[serde(default)]
364 evidence: Vec<Evidence>,
365 },
366 Failed {
367 command_id: CommandId,
368 error: CommandError,
369 #[serde(default)]
370 evidence: Vec<Evidence>,
371 },
372}
373
374pub const MAX_WAIT_OBSERVED_CHARS: usize = 512;
381
382#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
384#[serde(rename_all = "camelCase")]
385pub enum SubmitSettlementOutcome {
386 Settled,
387 ValidationRejected,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
391#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
392#[serde(
393 tag = "kind",
394 rename_all = "camelCase",
395 rename_all_fields = "camelCase"
396)]
397pub enum Evidence {
398 ExecutionPath {
399 path: ExecutionPath,
400 reason: ExecutionReason,
401 state_version: u64,
402 elapsed_ms: u64,
403 bytes: Option<u64>,
404 sha256: Option<String>,
405 #[serde(default, skip_serializing_if = "Option::is_none")]
406 final_url: Option<String>,
407 #[serde(default, skip_serializing_if = "Option::is_none")]
408 content_type: Option<String>,
409 #[serde(default, skip_serializing_if = "Option::is_none")]
410 status: Option<u16>,
411 #[serde(default, skip_serializing_if = "Vec::is_empty")]
412 redirect_chain: Vec<String>,
413 },
414 Navigation {
415 url: String,
416 title: String,
417 },
418 Inspection {
419 selector: Option<String>,
420 url: String,
421 title: String,
422 text: String,
423 html: Option<String>,
424 },
425 SubmitSettlement {
429 outcome: SubmitSettlementOutcome,
430 },
431 Element {
432 selector: String,
433 text: Option<String>,
434 },
435 Upload {
436 selector: String,
437 paths: Vec<String>,
438 },
439 Page {
440 page_id: PageId,
441 url: String,
442 title: String,
443 },
444 PageGeneration {
446 page_id: PageId,
447 generation: u64,
448 },
449 Pages {
450 pages: Vec<PageEvidence>,
451 },
452 Popup {
453 opener_page_id: PageId,
454 page_id: PageId,
455 url: String,
456 title: String,
457 },
458 PopupClosed {
465 popup_page_id: PageId,
466 opener_page_id: PageId,
467 },
468 Download {
469 filename: String,
470 path: String,
471 bytes: u64,
472 sha256: String,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
478 saved_to: Option<String>,
479 },
480 Configuration {
481 name: String,
482 value: String,
483 },
484 Resolution {
485 target: Box<crate::TargetSpec>,
486 fingerprint: Box<TargetFingerprint>,
487 candidates: Vec<CandidateEvidence>,
488 best_match_authorized: bool,
489 },
490 Wait {
491 condition: crate::WaitCondition,
492 elapsed_ms: u64,
493 observations: u64,
494 #[serde(default, skip_serializing_if = "Vec::is_empty")]
495 excluded_classes: Vec<String>,
496 #[serde(default, skip_serializing_if = "Option::is_none")]
505 observed: Option<String>,
506 },
507 Screenshot {
508 artifact_id: String,
509 media_type: String,
510 width: u32,
511 height: u32,
512 bytes: u64,
513 sha256: String,
514 },
515 BrowserExecution {
516 engine: String,
517 browser_version: String,
518 profile_id: String,
519 interaction_path: String,
520 },
521 JavaScriptResult {
522 value: serde_json::Value,
523 truncated: bool,
524 },
525 AccessibilitySnapshot {
526 page_id: PageId,
527 nodes: Vec<AccessibilityNode>,
528 truncated: bool,
529 },
530 FormSnapshot {
531 snapshot: crate::FormSnapshot,
532 },
533 FormValidation {
534 issues: Vec<crate::FormValidationIssue>,
535 },
536 ControlAction {
537 action: ControlActionEvidence,
538 },
539 StructuredExtraction {
540 page_id: PageId,
541 value: serde_json::Value,
542 truncated: bool,
543 },
544 ChallengeDetection {
551 confidence: f32,
552 detection: Option<crate::challenges::ChallengeDetection>,
553 #[serde(default, skip_serializing_if = "Option::is_none")]
554 prior_kind: Option<String>,
555 },
556 CookieState {
557 page_id: Option<PageId>,
558 cookies: Vec<crate::CookieRecord>,
559 },
560 PdfArtifact {
561 artifact_id: String,
562 media_type: String,
563 bytes: u64,
564 sha256: String,
565 },
566 Dialog {
567 dialog_type: String,
568 message: String,
569 action: String,
570 },
571 Emulation {
572 viewport: Option<crate::ViewportSize>,
573 geolocation: Option<crate::GeolocationCoordinates>,
574 },
575 HarArtifact {
576 artifact_id: String,
577 media_type: String,
578 bytes: u64,
579 sha256: String,
580 entries: u32,
581 },
582 IntentExecution {
583 record: ExecutionRecord,
584 },
585 Humanization {
590 engine: String,
591 actions: u32,
592 synthesized_ms: u64,
593 },
594 Extraction {
598 field: String,
599 #[serde(default, skip_serializing_if = "Option::is_none")]
600 value: Option<String>,
601 resolution_path: IntentResolutionPath,
602 #[serde(default, skip_serializing_if = "Option::is_none")]
603 error_code: Option<ErrorCode>,
604 },
605}
606
607#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
608#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
609#[serde(rename_all = "camelCase")]
610pub enum IntentResolutionPath {
611 Deterministic,
612 VisionFallback,
613 VisionPrefill,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
618#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
619#[serde(rename_all = "camelCase")]
620pub struct ExecutionRecord {
621 pub intent_kind: String,
622 pub purpose: Option<String>,
623 pub resolution_path: IntentResolutionPath,
624 pub plan_summary: String,
625 pub candidates: Vec<CandidateEvidence>,
626 pub wait_elapsed_ms: Option<u64>,
627 pub verification: String,
628 pub artifact_ids: Vec<String>,
629 pub vision_proposal_sha256: Option<String>,
630}
631
632impl Evidence {
633 pub fn page_derived(&self) -> bool {
634 matches!(
635 self,
636 Self::Inspection { .. }
637 | Self::AccessibilitySnapshot { .. }
638 | Self::FormSnapshot { .. }
639 | Self::StructuredExtraction { .. }
640 )
641 }
642
643 pub fn journal_safe(&self) -> Self {
644 fn safe_url(value: &str) -> String {
645 let Ok(mut url) = url::Url::parse(value) else {
646 return "[redacted-invalid-url]".into();
647 };
648 let _ = url.set_username("");
649 let _ = url.set_password(None);
650 url.set_query(None);
651 url.set_fragment(None);
652 url.to_string()
653 }
654 let mut safe = self.clone();
655 match &mut safe {
656 Self::ExecutionPath {
657 final_url,
658 redirect_chain,
659 ..
660 } => {
661 if let Some(url) = final_url {
662 *url = safe_url(url);
663 }
664 for url in redirect_chain {
665 *url = safe_url(url);
666 }
667 }
668 Self::Navigation { url, .. }
669 | Self::Inspection { url, .. }
670 | Self::Page { url, .. }
671 | Self::Popup { url, .. } => *url = safe_url(url),
672 Self::Pages { pages } => {
673 for page in pages {
674 page.url = safe_url(&page.url);
675 }
676 }
677 Self::Upload { paths, .. } => {
678 for (index, path) in paths.iter_mut().enumerate() {
679 *path = format!("upload://evidence/{index}");
680 }
681 }
682 Self::Download {
683 path,
684 sha256,
685 saved_to,
686 ..
687 } => {
688 *path = format!("artifact://sha256/{sha256}");
689 *saved_to = None;
690 }
691 Self::PopupClosed { .. } => {}
692 Self::BrowserExecution { .. } => {}
693 Self::IntentExecution { .. } => {}
694 Self::Extraction { .. } => {}
695 _ => {}
696 }
697 safe
698 }
699}
700
701pub fn annotate_page_derived_evidence(outcome: &mut serde_json::Value) {
705 let Some(evidence) = outcome
706 .get_mut("evidence")
707 .and_then(serde_json::Value::as_array_mut)
708 else {
709 return;
710 };
711 for item in evidence {
712 if matches!(
713 item.get("kind").and_then(serde_json::Value::as_str),
714 Some("inspection" | "accessibilitySnapshot" | "formSnapshot" | "structuredExtraction")
715 ) {
716 item["pageDerived"] = serde_json::Value::Bool(true);
717 }
718 }
719}
720
721impl CommandOutcome {
722 pub fn journal_safe(&self) -> Self {
723 let mut safe = self.clone();
724 match &mut safe {
725 Self::Completed { evidence, .. }
726 | Self::NeedsReconciliation { evidence, .. }
727 | Self::Restarted { evidence, .. }
728 | Self::Failed { evidence, .. } => {
729 *evidence = evidence.iter().map(Evidence::journal_safe).collect();
730 }
731 _ => {}
732 }
733 match &mut safe {
734 Self::RetryableFailure { error, .. }
735 | Self::NeedsReconciliation { error, .. }
736 | Self::PolicyDenied { error, .. }
737 | Self::ResourceExhausted { error, .. }
738 | Self::Failed { error, .. } => error.message = "redacted durable diagnostic".into(),
739 _ => {}
740 }
741 safe
742 }
743}
744
745#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
746#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
747#[serde(rename_all = "camelCase")]
748pub enum ExecutionPath {
754 DirectHttp,
755 #[serde(alias = "chromium")]
756 Browser,
757 #[serde(alias = "chromiumFallback")]
758 BrowserFallback,
759}
760
761#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
762#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
763#[serde(rename_all = "camelCase")]
764pub enum ExecutionReason {
765 EligibleStaticDocument,
766 EligibleExplicitDownload,
767 IneligibleCommand,
768 SemanticTargetRequired,
769 JavascriptRequired,
770 UnsupportedContentType,
771 StateConflict,
772 PolicyRequired,
773 PageMutated,
776}
777
778#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
779#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
780#[serde(rename_all = "camelCase")]
781pub struct CandidateEvidence {
782 pub role: Option<String>,
783 pub name: Option<String>,
784 pub score: i32,
785 pub reasons: Vec<String>,
786}
787
788#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
789#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
790#[serde(rename_all = "camelCase")]
791pub struct TargetFingerprint {
792 pub page_id: PageId,
793 pub frame: Option<String>,
794 pub role: Option<String>,
795 pub name: Option<String>,
796 pub stable_attributes: std::collections::BTreeMap<String, String>,
797}
798
799#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
800#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
801#[serde(rename_all = "camelCase")]
802pub struct PageEvidence {
803 pub page_id: PageId,
804 pub url: String,
805 pub title: String,
806}
807
808#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
809#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
810#[serde(rename_all = "camelCase")]
811pub struct CommandError {
812 pub code: ErrorCode,
813 pub message: String,
814 pub layer: ErrorLayer,
815 pub retryable: bool,
816}
817
818#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
819#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
820#[serde(rename_all = "camelCase")]
821pub enum ErrorCode {
822 InvalidRequest,
823 NotFound,
824 DeadlineExceeded,
825 BrowserLaunchFailed,
826 BrowserCommandFailed,
827 VerificationFailed,
828 JournalFailed,
829 ResourceExhausted,
830 PolicyDenied,
831 Internal,
832 TargetNotFound,
833 TargetAmbiguous,
834 FrameNotFound,
835 ShadowRootUnavailable,
836 TargetDetached,
837 TargetObscured,
838 TargetOutOfBounds,
839 WaitConditionTimedOut,
840 ScreenshotCaptureFailed,
841 NetworkPolicyDenied,
842 HttpResponseTooLarge,
843 HttpTransferFailed,
844 HttpStateConflict,
845 HttpEquivalenceUnproven,
846 IntentCompileFailed,
847 IntentActionMismatch,
848 ObstructionSuspected,
849 VisionAssistDenied,
850 VisionAssistFailed,
851 ExpectedStatePreSatisfied,
854 BoundaryAlreadyExecuted,
858}
859
860#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
861#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
862#[serde(rename_all = "camelCase")]
863pub enum ErrorLayer {
864 Interface,
865 Broker,
866 Workflow,
867 Page,
868 Driver,
869 Browser,
870 Network,
871 Site,
872 Journal,
873}
874
875#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
876#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
877#[serde(rename_all = "camelCase")]
878pub enum CommandPhase {
879 Accepted,
880 Prepared,
881 Executing,
882 ResultPrepared,
883 Verifying,
884 Recovering,
885 Completed,
886 Failed,
887}
888
889#[derive(Debug, Error, Serialize, Deserialize)]
890#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
891pub enum RuntimeError {
892 #[error("not found: {0}")]
893 NotFound(String),
894 #[error("invalid request: {0}")]
895 InvalidRequest(String),
896 #[error("{0}")]
900 EngineUnreachable(String),
901 #[error("internal error: {0}")]
902 Internal(String),
903}