victauri_plugin/mcp/other_params.rs
1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5// ── Enums ──────────────────────────────────────────────────────────────────
6
7/// Condition to poll for in the `wait_for` tool.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
9#[serde(rename_all = "snake_case")]
10pub enum WaitCondition {
11 /// Wait for text to appear in the page.
12 Text,
13 /// Wait for text to disappear from the page.
14 TextGone,
15 /// Wait for a CSS selector to match an element.
16 Selector,
17 /// Wait for a CSS selector to stop matching.
18 SelectorGone,
19 /// Wait for the URL to contain a substring.
20 Url,
21 /// Wait for all IPC calls to complete.
22 IpcIdle,
23 /// Wait for all network requests to complete.
24 NetworkIdle,
25 /// Poll a JavaScript expression until it is truthy (or equals `expected`).
26 ///
27 /// The expression is evaluated in the webview each poll via the same engine
28 /// as `eval_js`, so it may `await` (e.g.
29 /// `(await window.__TAURI_INTERNALS__.invoke('get_status')).running === false`).
30 /// This is the level-triggered, race-free way to await a fire-and-forget
31 /// backend command that exposes a pollable status — no app changes required.
32 Expression,
33 /// Block until a named Tauri event fires on the app's event bus.
34 ///
35 /// Edge-triggered completion: evaluated server-side against Victauri's
36 /// captured event bus, with a `since_ms` look-back so an event that fired in
37 /// the gap between `invoke_command` and this call is not missed. The app must
38 /// emit the event and Victauri must be configured to capture it via
39 /// `VictauriBuilder::listen_events(&["..."])` (window-lifecycle events are
40 /// captured automatically).
41 Event,
42}
43
44impl WaitCondition {
45 /// Returns the `snake_case` string for JS bridge consumption.
46 #[must_use]
47 pub fn as_str(self) -> &'static str {
48 match self {
49 Self::Text => "text",
50 Self::TextGone => "text_gone",
51 Self::Selector => "selector",
52 Self::SelectorGone => "selector_gone",
53 Self::Url => "url",
54 Self::IpcIdle => "ipc_idle",
55 Self::NetworkIdle => "network_idle",
56 Self::Expression => "expression",
57 Self::Event => "event",
58 }
59 }
60}
61
62impl fmt::Display for WaitCondition {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.write_str(self.as_str())
65 }
66}
67
68// ── Intent ─────────────────────────────────────────────────────────────────
69
70/// Parameters for the `resolve_command` tool.
71#[derive(Debug, Deserialize, JsonSchema)]
72pub struct ResolveCommandParams {
73 /// Natural language query describing what you want to do (e.g. "save the user's settings").
74 pub query: String,
75 /// Maximum number of results to return. Default: 5.
76 pub limit: Option<usize>,
77}
78
79/// Parameters for the `semantic_assert` tool.
80#[derive(Debug, Deserialize, JsonSchema)]
81pub struct SemanticAssertParams {
82 /// JavaScript expression to evaluate in the webview. The result is checked against the assertion.
83 pub expression: String,
84 /// Human-readable label for this assertion (e.g. "user is logged in").
85 /// Optional — defaults to empty so a minimal `{expression, condition}` call
86 /// succeeds instead of failing deserialization with an opaque 400.
87 #[serde(default)]
88 pub label: String,
89 /// Condition to evaluate against the actual value.
90 pub condition: victauri_core::AssertionCondition,
91 /// Expected value for the assertion. Optional for truthy/falsy/exists conditions.
92 #[serde(default)]
93 pub expected: serde_json::Value,
94 /// Target webview label.
95 pub webview_label: Option<String>,
96}
97
98// ── Wait ───────────────────────────────────────────────────────────────────
99
100/// Parameters for the `wait_for` tool.
101#[derive(Debug, Deserialize, JsonSchema)]
102pub struct WaitForParams {
103 /// Condition to wait for.
104 pub condition: WaitCondition,
105 /// Value for the condition: text to find, CSS selector, URL substring,
106 /// JS expression (for `expression`), or Tauri event name (for `event`).
107 pub value: Option<String>,
108 /// Maximum time to wait in milliseconds. Default: 10000.
109 pub timeout_ms: Option<u64>,
110 /// Polling interval in milliseconds. Default: 200.
111 pub poll_ms: Option<u64>,
112 /// For the `expression` condition: the JSON value the expression must equal
113 /// to satisfy the wait. When omitted, the wait is satisfied as soon as the
114 /// expression evaluates to a truthy value.
115 #[serde(default)]
116 pub expected: Option<serde_json::Value>,
117 /// For the `event` condition: how far back (in milliseconds) to look for a
118 /// matching event when the wait begins, so an event that fired just before
119 /// this call is not missed. Default: 2000.
120 pub since_ms: Option<u64>,
121 /// Target webview label.
122 pub webview_label: Option<String>,
123}
124
125// ── App State Probes ─────────────────────────────────────────────────────────
126
127/// Parameters for the `app_state` tool.
128#[derive(Debug, Deserialize, JsonSchema)]
129pub struct AppStateParams {
130 /// Name of the probe to run. When omitted, lists all available probe names.
131 pub probe: Option<String>,
132}
133
134// ── Find Elements ──────────────────────────────────────────────────────────
135
136/// Parameters for the `find_elements` tool.
137#[derive(Debug, Deserialize, JsonSchema)]
138pub struct FindElementsParams {
139 /// Text content to search for (case-insensitive substring match).
140 pub text: Option<String>,
141 /// ARIA role to match (exact match).
142 pub role: Option<String>,
143 /// data-testid attribute value to match (exact match).
144 pub test_id: Option<String>,
145 /// CSS selector to match (also accepts `selector` as an alias).
146 pub css: Option<String>,
147 /// Alias for `css` — CSS selector to match.
148 pub selector: Option<String>,
149 /// Accessible name to search for (aria-label, title, placeholder -- case-insensitive substring).
150 pub name: Option<String>,
151 /// Maximum number of results to return. Default: 10.
152 pub max_results: Option<u32>,
153 /// HTML tag name to match (e.g. "button", "input").
154 pub tag: Option<String>,
155 /// Placeholder text to match (case-insensitive substring).
156 pub placeholder: Option<String>,
157 /// Alt text to match (case-insensitive substring).
158 pub alt: Option<String>,
159 /// Title attribute to match (case-insensitive substring).
160 pub title_attr: Option<String>,
161 /// Associated label text to match (finds inputs by their label).
162 pub label: Option<String>,
163 /// If true, text matching is exact instead of substring.
164 pub exact: Option<bool>,
165 /// Filter by enabled state.
166 pub enabled: Option<bool>,
167 /// Target webview label.
168 pub webview_label: Option<String>,
169}
170
171/// Parameters for the `get_diagnostics` tool.
172#[derive(Debug, Deserialize, JsonSchema)]
173pub struct DiagnosticsParams {
174 /// Target a specific webview window by label.
175 pub webview_label: Option<String>,
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn semantic_assert_params_label_is_optional() {
184 // Regression: `label` was a required `String`, so a minimal
185 // `{expression, condition}` call failed deserialization with an opaque
186 // 400. It is now `#[serde(default)]` and must default to empty.
187 let params: SemanticAssertParams = serde_json::from_value(serde_json::json!({
188 "expression": "1 + 1",
189 "condition": "equals",
190 "expected": 2
191 }))
192 .expect("minimal assert_semantic call (no label) must deserialize");
193 assert_eq!(params.label, "");
194 assert_eq!(params.expression, "1 + 1");
195 assert!(params.webview_label.is_none());
196 }
197
198 #[test]
199 fn semantic_assert_params_label_still_accepted() {
200 let params: SemanticAssertParams = serde_json::from_value(serde_json::json!({
201 "expression": "x",
202 "label": "user is logged in",
203 "condition": "truthy"
204 }))
205 .expect("explicit label must still deserialize");
206 assert_eq!(params.label, "user is logged in");
207 }
208}