Skip to main content

purview/
snapshot.rs

1//! Wire data for the protocol (§4.2 snapshot) and the action success result
2//! (§5.4 `Outcome`).
3
4use serde::Serialize;
5use serde_json::Value;
6
7use crate::block::{Block, to_value};
8
9/// The `app://windows` snapshot (§4.2). Field names are camelCase on the wire.
10#[derive(Debug, Clone, Serialize)]
11#[serde(rename_all = "camelCase")]
12pub struct WindowsSnapshot {
13    /// The sole authoritative set of currently operable window ids.
14    pub operable_window_ids: Vec<String>,
15    /// All windows, each inlining its content.
16    pub windows: Vec<WindowOut>,
17}
18
19/// A single window inside the snapshot.
20#[derive(Debug, Clone, Serialize)]
21#[serde(rename_all = "camelCase")]
22pub struct WindowOut {
23    /// Globally unique, never-reused window id.
24    pub id: String,
25    /// Window title (human-readable).
26    pub title: String,
27    /// Whether the window is modal.
28    pub modal: bool,
29    /// Parent window id; `null` for a top-level or app-level modal window.
30    pub owner_id: Option<String>,
31    /// Optimistic-concurrency version (§7.1).
32    pub version: i64,
33    /// One-line summary of what the window currently shows.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub summary: Option<String>,
36    /// Window content as a flat sequence of semantic blocks.
37    pub blocks: Vec<Block>,
38}
39
40/// Action success result (§5.4). `openedWindowIds` is normally filled by the
41/// library; set it explicitly with [`Outcome::opened`] only when the auto
42/// tracking cannot see the open (e.g. a window opened from a spawned task).
43#[derive(Debug, Clone, Default, Serialize)]
44#[serde(rename_all = "camelCase")]
45pub struct Outcome {
46    /// Human-readable result message.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub message: Option<String>,
49    /// Ids of windows opened during the action (auto-tracked by the library).
50    #[serde(skip_serializing_if = "Vec::is_empty")]
51    pub opened_window_ids: Vec<String>,
52    /// Tool-specific structured business result (shape declared by
53    /// `.returns::<R>()`).
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub result: Option<Value>,
56}
57
58impl Outcome {
59    /// Build an outcome carrying just a message.
60    pub fn message(msg: impl Into<String>) -> Self {
61        Outcome {
62            message: Some(msg.into()),
63            opened_window_ids: Vec::new(),
64            result: None,
65        }
66    }
67
68    /// Explicitly set the ids of windows opened by this action. Overrides the
69    /// library's automatic tracking for this outcome.
70    pub fn opened<I, S>(mut self, ids: I) -> Self
71    where
72        I: IntoIterator<Item = S>,
73        S: Into<String>,
74    {
75        self.opened_window_ids = ids.into_iter().map(Into::into).collect();
76        self
77    }
78
79    /// Attach a tool-specific structured business result (its shape is
80    /// described by the tool's `outputSchema` from `.returns::<R>()`).
81    pub fn with(mut self, result: impl Serialize) -> Self {
82        self.result = Some(to_value(result));
83        self
84    }
85
86    /// Convert into the JSON placed in `structuredContent` (§5.4 / §12.3).
87    pub(crate) fn to_structured(&self) -> Value {
88        to_value(self)
89    }
90
91    /// Human-readable text mirror placed in `content` (§12.3).
92    pub(crate) fn text_mirror(&self) -> String {
93        self.message.clone().unwrap_or_default()
94    }
95}
96
97impl From<&str> for Outcome {
98    fn from(s: &str) -> Self {
99        Outcome::message(s)
100    }
101}
102
103impl From<String> for Outcome {
104    fn from(s: String) -> Self {
105        Outcome::message(s)
106    }
107}
108
109impl From<()> for Outcome {
110    fn from(_: ()) -> Self {
111        Outcome::default()
112    }
113}