node-app-sdk-rust 5.25.0

Rust SDK for building Node-App native plugins (cdylib) — Lightning-powered marketplace nodes
Documentation
//! Ergonomic shortcuts for building public LLM call requests (feature 472,
//! T1012). Resolves UI finding U2 — single-line construction of a request
//! with a specific routing intent.
//!
//! ```ignore
//! use node_app_sdk_rust::llm::{ExecuteChatRequest, ChatMessage};
//!
//! let req = ExecuteChatRequest {
//!     messages: vec![ChatMessage { role: "user".into(), content: "hi".into() }],
//!     model: Some("gpt-4o".into()),
//!     ..Default::default()
//! }
//! .private();
//! ```
//!
//! **Honest framing** (per S5/U4): Private mode isolates the upstream
//! provider's API credentials on the peer's host and routes egress through a
//! separate device; it does NOT mask prompt contents from the upstream
//! provider or anonymize the user.

use super::types::{ExecuteBestModelRequest, ExecuteChatRequest, LlmRoute};

// Provide a Default impl so callers can build via the
// `ExecuteChatRequest { messages, ..Default::default() }.private()` idiom.
impl Default for ExecuteChatRequest {
    fn default() -> Self {
        Self {
            messages: Vec::new(),
            model: None,
            platform: None,
            route: None,
            execution_preference: None,
            max_output_tokens: None,
            correlation_id: None,
            tools: None,
            tool_choice: None,
            temperature: None,
            response_format: None,
        }
    }
}

impl Default for ExecuteBestModelRequest {
    fn default() -> Self {
        Self {
            messages: Vec::new(),
            route: None,
            execution_preference: None,
            correlation_id: None,
        }
    }
}

/// Routing-intent shortcuts.
///
/// Implemented on every public-LLM request shape. Mirrors the TypeScript
/// SDK's `.route('public' | 'private' | 'auto')` ergonomics with idiomatic
/// Rust verbs (`.public()`, `.private()`, `.auto()`).
pub trait WithRoute: Sized {
    /// Set [`LlmRoute::Public`] — force direct upstream-provider execution,
    /// skipping any custodial-hardware hop even on peers that have it
    /// configured.
    fn public(self) -> Self;

    /// Set [`LlmRoute::Private`] — force the custodial-hardware path. Hard-
    /// fails with [`crate::llm::PrivateUnavailableError`] when the selected
    /// executor has no hardware path.
    ///
    /// **Honest framing**: Private mode isolates the upstream provider's API
    /// credentials on the peer's host and routes egress through a separate
    /// device; it does NOT mask prompt contents from the upstream provider
    /// or anonymize the user.
    fn private(self) -> Self;

    /// Set [`LlmRoute::Auto`] — pick the more-isolated path when available,
    /// never hard-fail because of routing. The response's `execution_route`
    /// is the truthful echo of what actually ran.
    fn auto(self) -> Self;
}

impl WithRoute for ExecuteChatRequest {
    fn public(mut self) -> Self {
        self.route = Some(LlmRoute::Public);
        self
    }
    fn private(mut self) -> Self {
        self.route = Some(LlmRoute::Private);
        self
    }
    fn auto(mut self) -> Self {
        self.route = Some(LlmRoute::Auto);
        self
    }
}

// `StreamChatRequest = ExecuteChatRequest` and `ExecuteRequest =
// ExecuteChatRequest` (type aliases), so the impl above already covers them
// transparently — no separate impl block needed.

impl WithRoute for ExecuteBestModelRequest {
    fn public(mut self) -> Self {
        self.route = Some(LlmRoute::Public);
        self
    }
    fn private(mut self) -> Self {
        self.route = Some(LlmRoute::Private);
        self
    }
    fn auto(mut self) -> Self {
        self.route = Some(LlmRoute::Auto);
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::llm::{ChatMessage, StreamChatRequest};

    fn msg() -> Vec<ChatMessage> {
        vec![ChatMessage {
            role: "user".into(),
            content: "hi".into(),
        }]
    }

    // ── T1012 — `.public()` / `.private()` / `.auto()` shortcuts ───────────

    #[test]
    fn execute_chat_request_public_shortcut_sets_route() {
        let req = ExecuteChatRequest {
            messages: msg(),
            ..Default::default()
        }
        .public();
        assert_eq!(req.route, Some(LlmRoute::Public));
    }

    #[test]
    fn execute_chat_request_private_shortcut_sets_route() {
        let req = ExecuteChatRequest {
            messages: msg(),
            ..Default::default()
        }
        .private();
        assert_eq!(req.route, Some(LlmRoute::Private));
    }

    #[test]
    fn execute_chat_request_auto_shortcut_sets_route() {
        let req = ExecuteChatRequest {
            messages: msg(),
            ..Default::default()
        }
        .auto();
        assert_eq!(req.route, Some(LlmRoute::Auto));
    }

    #[test]
    fn shortcut_serializes_route_as_wire_field_route_mode() {
        // Wire-form contract: `route_mode` snake_case, not `route`.
        let req = ExecuteChatRequest {
            messages: msg(),
            ..Default::default()
        }
        .private();
        let json = serde_json::to_string(&req).expect("serialize");
        assert!(
            json.contains("\"route_mode\":\"private\""),
            "wire field MUST be `route_mode`, got: {}",
            json
        );
        assert!(
            !json.contains("\"route\":"),
            "`route` is the Rust field name; the wire form must use route_mode"
        );
    }

    #[test]
    fn shortcuts_are_chainable_and_last_wins() {
        let req = ExecuteChatRequest {
            messages: msg(),
            ..Default::default()
        }
        .public()
        .auto()
        .private();
        assert_eq!(req.route, Some(LlmRoute::Private));
    }

    #[test]
    fn execute_best_model_request_shortcuts() {
        let public_req = ExecuteBestModelRequest {
            messages: msg(),
            ..Default::default()
        }
        .public();
        assert_eq!(public_req.route, Some(LlmRoute::Public));

        let private_req = ExecuteBestModelRequest {
            messages: msg(),
            ..Default::default()
        }
        .private();
        assert_eq!(private_req.route, Some(LlmRoute::Private));

        let auto_req = ExecuteBestModelRequest {
            messages: msg(),
            ..Default::default()
        }
        .auto();
        assert_eq!(auto_req.route, Some(LlmRoute::Auto));
    }

    #[test]
    fn shortcut_works_on_stream_chat_request_alias() {
        // StreamChatRequest is a type alias for ExecuteChatRequest, so the
        // shortcut impl applies transparently. Lock that with a test so a
        // future refactor that breaks the alias also breaks visibly.
        let req: StreamChatRequest = ExecuteChatRequest {
            messages: msg(),
            ..Default::default()
        }
        .private();
        assert_eq!(req.route, Some(LlmRoute::Private));
    }
}