Skip to main content

node_app_api/
types.rs

1//! Shared types used by both native and scripted app loaders
2//!
3//! These types are serialized to JSON for IPC (Bun loader) and
4//! converted to/from C representations for FFI (native loader).
5
6use bitflags::bitflags;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// App metadata returned during initialization
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct NodeAppInfo {
13    /// Human-readable app name (must be unique across installed apps)
14    pub name: String,
15    /// Semantic version string (e.g., "0.1.0")
16    pub version: String,
17    /// Author name or organization
18    pub author: String,
19    /// Brief description of the app's functionality
20    pub description: String,
21    /// Declared capabilities as string list (e.g., ["http_handler", "event_listener"])
22    pub capabilities: Vec<String>,
23}
24
25impl NodeAppInfo {
26    /// Parse capabilities strings into the Capabilities bitflag
27    pub fn capability_flags(&self) -> Capabilities {
28        let mut flags = Capabilities::empty();
29        for cap in &self.capabilities {
30            match cap.as_str() {
31                "http_handler" => flags |= Capabilities::HTTP_HANDLER,
32                "event_listener" => flags |= Capabilities::EVENT_LISTENER,
33                "filesystem_read" => flags |= Capabilities::FILESYSTEM_READ,
34                "filesystem_write" => flags |= Capabilities::FILESYSTEM_WRITE,
35                "network_outbound" => flags |= Capabilities::NETWORK_OUTBOUND,
36                "system_info" => flags |= Capabilities::SYSTEM_INFO,
37                "capability_provider" => flags |= Capabilities::CAPABILITY_PROVIDER,
38                _ => {
39                    eprintln!("[node-app-api] Unknown capability '{}', ignoring", cap);
40                }
41            }
42        }
43        flags
44    }
45}
46
47bitflags! {
48    /// Capability flags declaring what an app can do
49    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50    pub struct Capabilities: u32 {
51        /// App can handle proxied HTTP requests
52        const HTTP_HANDLER     = 0b0000_0001;  // Bit 0
53        /// App can receive domain events
54        const EVENT_LISTENER   = 0b0000_0010;  // Bit 1
55        /// App can read files from allowed paths
56        const FILESYSTEM_READ  = 0b0000_0100;  // Bit 2
57        /// App can write files to allowed paths
58        const FILESYSTEM_WRITE = 0b0000_1000;  // Bit 3
59        /// App can make outbound network requests
60        const NETWORK_OUTBOUND = 0b0001_0000;  // Bit 4
61        /// App can read system information (hostname, etc.)
62        const SYSTEM_INFO      = 0b0010_0000;  // Bit 5
63        /// App provides service capabilities to other apps
64        const CAPABILITY_PROVIDER = 0b0100_0000;  // Bit 6
65    }
66}
67
68impl Capabilities {
69    /// Convert to a list of capability strings for API responses
70    pub fn to_string_list(&self) -> Vec<String> {
71        let mut list = Vec::new();
72        if self.contains(Capabilities::HTTP_HANDLER) {
73            list.push("http_handler".to_string());
74        }
75        if self.contains(Capabilities::EVENT_LISTENER) {
76            list.push("event_listener".to_string());
77        }
78        if self.contains(Capabilities::FILESYSTEM_READ) {
79            list.push("filesystem_read".to_string());
80        }
81        if self.contains(Capabilities::FILESYSTEM_WRITE) {
82            list.push("filesystem_write".to_string());
83        }
84        if self.contains(Capabilities::NETWORK_OUTBOUND) {
85            list.push("network_outbound".to_string());
86        }
87        if self.contains(Capabilities::SYSTEM_INFO) {
88            list.push("system_info".to_string());
89        }
90        if self.contains(Capabilities::CAPABILITY_PROVIDER) {
91            list.push("capability_provider".to_string());
92        }
93        list
94    }
95
96    /// Parse from integer (used for database storage)
97    pub fn from_bits_safe(bits: u32) -> Self {
98        Capabilities::from_bits_truncate(bits)
99    }
100
101    /// Convert capability string to Capabilities flag
102    pub fn from_string(s: &str) -> Option<Self> {
103        match s.to_lowercase().as_str() {
104            "http_handler" => Some(Capabilities::HTTP_HANDLER),
105            "event_listener" => Some(Capabilities::EVENT_LISTENER),
106            "filesystem_read" => Some(Capabilities::FILESYSTEM_READ),
107            "filesystem_write" => Some(Capabilities::FILESYSTEM_WRITE),
108            "network_outbound" => Some(Capabilities::NETWORK_OUTBOUND),
109            "system_info" => Some(Capabilities::SYSTEM_INFO),
110            "capability_provider" => Some(Capabilities::CAPABILITY_PROVIDER),
111            _ => None,
112        }
113    }
114
115    /// Get all capability names as static strings
116    pub fn all_capability_names() -> &'static [&'static str] {
117        &[
118            "http_handler",
119            "event_listener",
120            "filesystem_read",
121            "filesystem_write",
122            "network_outbound",
123            "system_info",
124            "capability_provider",
125        ]
126    }
127}
128
129/// Caller identity injected by the host proxy after JWT validation.
130/// Only present on requests that came through the scoped-JWT proxy route.
131/// Apps should trust this over any HTTP headers (headers are stripped by the proxy).
132#[derive(Debug, Clone, Serialize, Deserialize, Default)]
133pub struct CallerContext {
134    /// The app name whose scoped JWT was used (always equals the receiving app's name)
135    pub app_name: String,
136    /// Permissions granted by the user at consent time and embedded in the scoped JWT
137    pub granted_permissions: Vec<String>,
138}
139
140/// HTTP request forwarded to an app
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct AppRequest {
143    /// Unique request identifier for correlation
144    pub id: String,
145    /// HTTP method (GET, POST, PUT, DELETE, PATCH)
146    pub method: String,
147    /// Sub-path within the app (e.g., "/hello", "/stats")
148    pub path: String,
149    /// HTTP headers as key-value pairs (x-node-* headers are stripped by the proxy)
150    pub headers: HashMap<String, String>,
151    /// Request body as JSON value
152    pub body: serde_json::Value,
153    /// Caller identity set by the host proxy after scoped JWT validation.
154    /// None for direct (non-proxy) requests such as internal capability calls.
155    #[serde(default)]
156    pub caller: Option<CallerContext>,
157}
158
159/// HTTP response from an app
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct AppResponse {
162    /// HTTP status code
163    pub status: u16,
164    /// Response headers
165    pub headers: HashMap<String, String>,
166    /// Response body as JSON value
167    pub body: serde_json::Value,
168}
169
170/// Domain event forwarded to apps
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct AppEvent {
173    /// Event name (e.g., "payment_received", "channel_opened")
174    pub name: String,
175    /// Event data as JSON value
176    pub data: serde_json::Value,
177}
178
179// ── Capability System Types (337-core-capability-registry) ──────────
180
181/// A service capability request dispatched to a provider app.
182/// Used for both FFI (serialized as JSON bytes) and IPC (serialized as JSON).
183#[derive(Debug, Clone, Serialize, Deserialize, Default)]
184pub struct CapabilityRequest {
185    /// Request correlation ID (UUID)
186    pub id: String,
187    /// Target capability name (dot-notation, e.g., "terminal.create_session")
188    pub capability: String,
189    /// Request payload as JSON value
190    pub payload: serde_json::Value,
191    /// Optional per-capability timeout in milliseconds (falls back to 30s default)
192    #[serde(default, skip_serializing_if = "Option::is_none")]
193    pub timeout_ms: Option<u64>,
194    // Distributed tracing fields (426-capability-tracing-visualization)
195    /// Root trace ID grouping all spans in one logical invocation.
196    #[serde(default, skip_serializing_if = "Option::is_none")]
197    pub trace_id: Option<String>,
198    /// Span ID of the caller (used as parent_span_id in sub-invocations).
199    #[serde(default, skip_serializing_if = "Option::is_none")]
200    pub span_id: Option<String>,
201    /// Parent span ID for this invocation (None if root).
202    #[serde(default, skip_serializing_if = "Option::is_none")]
203    pub parent_span_id: Option<String>,
204    /// Current nesting depth (0 for root, max 16).
205    #[serde(default, skip_serializing_if = "Option::is_none")]
206    pub trace_depth: Option<u8>,
207}
208
209/// Response from a provider app after handling a capability request.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct CapabilityResponse {
212    /// Correlation ID matching the request
213    pub id: String,
214    /// Whether the invocation succeeded
215    pub success: bool,
216    /// Response data (on success) or error details (on failure)
217    pub payload: serde_json::Value,
218}
219
220/// Declaration of a service capability that an app provides.
221/// Used in app manifests and registration APIs.
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct ProvidedCapability {
224    /// Dot-notation capability name (e.g., "terminal.create_session")
225    pub name: String,
226    /// Human-readable description of what this capability does
227    pub description: String,
228    /// JSON Schema for request payload validation (None = no validation)
229    #[serde(default)]
230    pub request_schema: Option<serde_json::Value>,
231    /// JSON Schema for response payload validation (None = no validation)
232    #[serde(default)]
233    pub response_schema: Option<serde_json::Value>,
234    /// Provider priority (lower = preferred, default 100)
235    #[serde(default = "default_capability_priority")]
236    pub priority: i32,
237    /// Example request payloads for developer documentation and testing
238    #[serde(default)]
239    pub examples: Vec<CapabilityExample>,
240}
241
242/// An example request payload for a capability.
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct CapabilityExample {
245    /// Short label describing the example (e.g., "Insert a document")
246    pub label: String,
247    /// Example request payload
248    pub request: serde_json::Value,
249}
250
251fn default_capability_priority() -> i32 {
252    100
253}