Skip to main content

soma_palette/
catalog.rs

1//! Product mapping from provider `ToolSpec` / Palette overlays into Palette
2//! launcher actions, plus in-memory search over the mapped catalog.
3
4use soma_application::CatalogSnapshot;
5use soma_provider_core::{ProviderSurface, ToolSpec};
6
7use crate::dto::{LauncherCatalogEntry, LauncherCatalogResponse};
8
9/// Map every palette-exposed tool across every catalog in `snapshot` into a
10/// [`LauncherCatalogEntry`]. A tool is included when it exposes itself on the
11/// Palette surface, per `ToolSpec::exposed_on` (the `palette` overlay
12/// defaults to enabled when absent).
13#[must_use]
14pub fn palette_entries(snapshot: &CatalogSnapshot) -> Vec<LauncherCatalogEntry> {
15    snapshot
16        .catalogs
17        .iter()
18        .flat_map(|catalog| {
19            catalog
20                .tools
21                .iter()
22                .filter(|tool| tool.exposed_on(ProviderSurface::Palette))
23                .map(|tool| tool_to_entry(&catalog.provider.name, tool))
24        })
25        .collect()
26}
27
28/// Build the full catalog response for `GET /v1/palette/catalog`.
29#[must_use]
30pub fn catalog_response(snapshot: &CatalogSnapshot) -> LauncherCatalogResponse {
31    LauncherCatalogResponse {
32        schema_version: 1,
33        fingerprint: snapshot.fingerprint.clone(),
34        entries: palette_entries(snapshot),
35    }
36}
37
38fn tool_to_entry(provider: &str, tool: &ToolSpec) -> LauncherCatalogEntry {
39    let overlay = tool.palette.as_ref();
40    LauncherCatalogEntry {
41        id: tool.name.clone(),
42        provider: provider.to_owned(),
43        title: tool.title.clone().unwrap_or_else(|| tool.name.clone()),
44        description: tool.description.clone(),
45        category: overlay.and_then(|overlay| overlay.category.clone()),
46        icon: overlay.and_then(|overlay| overlay.icon.clone()),
47        tone: overlay.and_then(|overlay| overlay.tone.clone()),
48        arg_mode: overlay.and_then(|overlay| overlay.arg_mode.clone()),
49        result_view: overlay.and_then(|overlay| overlay.result_view.clone()),
50        destructive: tool.destructive,
51        requires_admin: tool.requires_admin,
52    }
53}
54
55#[cfg(test)]
56#[path = "catalog_tests.rs"]
57mod tests;