adk_computer_use/contracts/
target.rs1use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::HashSet;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct TargetEvidence {
15 pub platform: String,
17 pub app_id: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub pid: Option<u32>,
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub window_id: Option<Value>,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub window_title_digest: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub display_id: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub role: Option<String>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub label_digest: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub bounds: Option<Bounds>,
40 pub observation_id: String,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub screenshot_hash: Option<String>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub ui_tree_revision: Option<String>,
48 pub confidence: f64,
50 pub captured_at: String,
52}
53
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct Bounds {
58 pub x: f64,
60 pub y: f64,
62 pub width: f64,
64 pub height: f64,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "snake_case")]
71pub enum TargetSensitivityAssessment {
72 Sensitive,
74 NonSensitive,
76 Unknown,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "snake_case")]
83pub enum TargetSensitivitySource {
84 Accessibility,
86 Unavailable,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
92#[serde(rename_all = "snake_case")]
93pub enum TargetSensitivitySignal {
94 SecureRole,
96 SecureSubrole,
98 ProtectedContent,
100 UiaIsPassword,
102 SensitiveLabel,
104 AmbiguousMatch,
106 ElementNotFound,
108 InspectionError,
110 InvalidField,
112 NativeSignalUnavailable,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122#[serde(try_from = "RawTargetSensitivityEvidence", into = "RawTargetSensitivityEvidence")]
123pub struct TargetSensitivityEvidence {
124 assessment: TargetSensitivityAssessment,
125 source: TargetSensitivitySource,
126 signals: Vec<TargetSensitivitySignal>,
127 fields_checked: u32,
128 observed_at: String,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase", deny_unknown_fields)]
133struct RawTargetSensitivityEvidence {
134 assessment: TargetSensitivityAssessment,
135 source: TargetSensitivitySource,
136 signals: Vec<TargetSensitivitySignal>,
137 fields_checked: u32,
138 observed_at: String,
139}
140
141impl TargetSensitivityEvidence {
142 pub fn try_new(
170 assessment: TargetSensitivityAssessment,
171 source: TargetSensitivitySource,
172 signals: Vec<TargetSensitivitySignal>,
173 fields_checked: u32,
174 observed_at: impl Into<String>,
175 ) -> Result<Self, String> {
176 RawTargetSensitivityEvidence {
177 assessment,
178 source,
179 signals,
180 fields_checked,
181 observed_at: observed_at.into(),
182 }
183 .try_into()
184 }
185
186 pub fn assessment(&self) -> TargetSensitivityAssessment {
188 self.assessment
189 }
190
191 pub fn source(&self) -> TargetSensitivitySource {
193 self.source
194 }
195
196 pub fn signals(&self) -> &[TargetSensitivitySignal] {
198 &self.signals
199 }
200
201 pub fn fields_checked(&self) -> u32 {
203 self.fields_checked
204 }
205
206 pub fn observed_at(&self) -> &str {
208 &self.observed_at
209 }
210}
211
212impl TryFrom<RawTargetSensitivityEvidence> for TargetSensitivityEvidence {
213 type Error = String;
214
215 fn try_from(raw: RawTargetSensitivityEvidence) -> Result<Self, Self::Error> {
216 if raw.signals.len() > 10 {
217 return Err("target sensitivity supports at most 10 signals".into());
218 }
219 if raw.signals.iter().copied().collect::<HashSet<_>>().len() != raw.signals.len() {
220 return Err("target sensitivity signals must be unique".into());
221 }
222 if raw.fields_checked > 100 {
223 return Err("target sensitivity supports at most 100 checked fields".into());
224 }
225 if matches!(
226 raw.assessment,
227 TargetSensitivityAssessment::Sensitive | TargetSensitivityAssessment::NonSensitive
228 ) && (raw.source != TargetSensitivitySource::Accessibility || raw.fields_checked == 0)
229 {
230 return Err(
231 "conclusive target sensitivity requires accessibility evidence for a checked field"
232 .into(),
233 );
234 }
235 if raw.assessment == TargetSensitivityAssessment::Sensitive && raw.signals.is_empty() {
236 return Err("sensitive target evidence requires at least one signal".into());
237 }
238 if raw.observed_at.trim().is_empty() {
239 return Err("target sensitivity observedAt must not be empty".into());
240 }
241 Ok(Self {
242 assessment: raw.assessment,
243 source: raw.source,
244 signals: raw.signals,
245 fields_checked: raw.fields_checked,
246 observed_at: raw.observed_at,
247 })
248 }
249}
250
251impl From<TargetSensitivityEvidence> for RawTargetSensitivityEvidence {
252 fn from(value: TargetSensitivityEvidence) -> Self {
253 Self {
254 assessment: value.assessment,
255 source: value.source,
256 signals: value.signals,
257 fields_checked: value.fields_checked,
258 observed_at: value.observed_at,
259 }
260 }
261}