Skip to main content

soma_palette/
dto.rs

1//! Palette DTOs shared by the Soma HTTP server and the desktop app.
2//!
3//! These are plain, transport-neutral data shapes — `crates/soma/palette`
4//! owns the mapping *into* these from provider catalogs, and the routes that
5//! serialize them, but the shapes themselves carry no server or Tauri
6//! dependency so a desktop client can mirror them.
7
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11/// One launcher-visible action, derived from a provider `ToolSpec` whose
12/// `palette` overlay exposes it (or exposes it by default).
13#[derive(Debug, Clone, PartialEq, Serialize)]
14#[serde(rename_all = "camelCase")]
15pub struct LauncherCatalogEntry {
16    /// The action id to pass to `/v1/palette/execute`. Stable across catalog
17    /// refreshes as long as the underlying tool name doesn't change.
18    pub id: String,
19    pub provider: String,
20    pub title: String,
21    pub description: String,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub category: Option<String>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub icon: Option<String>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub tone: Option<String>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub arg_mode: Option<String>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub result_view: Option<String>,
32    pub destructive: bool,
33    pub requires_admin: bool,
34}
35
36#[derive(Debug, Clone, PartialEq, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct LauncherCatalogResponse {
39    pub schema_version: u32,
40    pub fingerprint: String,
41    pub entries: Vec<LauncherCatalogEntry>,
42}
43
44#[derive(Debug, Clone, Default, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct LauncherSearchQuery {
47    #[serde(default)]
48    pub q: String,
49    #[serde(default)]
50    pub limit: Option<usize>,
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize)]
54#[serde(rename_all = "camelCase")]
55pub struct LauncherSearchResponse {
56    pub entries: Vec<LauncherCatalogEntry>,
57}
58
59#[derive(Debug, Clone, Deserialize)]
60pub struct LauncherSchemaQuery {
61    pub id: String,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize)]
65#[serde(rename_all = "camelCase")]
66pub struct LauncherSchemaResponse {
67    pub id: String,
68    pub input_schema: Value,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub output_schema: Option<Value>,
71}
72
73#[derive(Debug, Clone, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct LauncherExecuteRequest {
76    pub id: String,
77    /// Defaults to an empty object, not `Value::Null` (`Value::default()`) —
78    /// provider input schemas validate against object-shaped schemas, so a
79    /// zero-argument action (e.g. `status`) would otherwise fail dispatch
80    /// with `input_schema_failed` whenever a client omits `params` entirely.
81    #[serde(default = "default_params")]
82    pub params: Value,
83    #[serde(default)]
84    pub confirm_destructive: bool,
85}
86
87fn default_params() -> Value {
88    Value::Object(serde_json::Map::new())
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct LauncherExecuteResponse {
94    pub output: Value,
95    pub request_id: String,
96}
97
98#[cfg(test)]
99#[path = "dto_tests.rs"]
100mod tests;