car-ffi-common 0.6.0

Shared logic for FFI bindings (NAPI, PyO3) — JSON wrappers for verify, multi-agent, scheduler
Documentation
//! Shared logic for FFI bindings (NAPI, PyO3).
//!
//! Pure Rust functions that accept JSON strings and return `Result<String, String>`.
//! Each FFI crate wraps these with its own error type (napi::Error, PyErr).

/// Initialize a `tracing_subscriber` honoring `RUST_LOG`. Idempotent
/// — subsequent calls return immediately. Used by FFI bindings to
/// surface `[voice] dropped …` and other crate-side warn/error lines
/// when callers set `RUST_LOG=car_voice=debug` (or similar).
///
/// Without this call, `tracing::warn!`/`error!` lines emitted from
/// car-voice / car-inference / etc. go nowhere in the FFI binary —
/// `#120`'s diagnostic dead-end. We can't `init` from a binding's
/// `[lib]` ctor (no static init in cdylib that's safe for tracing),
/// so each entry-point function calls this and the OnceLock guard
/// makes it cheap.
///
/// Falls back silently if another global subscriber is already set
/// (e.g., when the embedder loads `car-server` and the FFI module
/// in the same process).
pub fn ensure_tracing_subscriber() {
    use std::sync::OnceLock;
    static INIT: OnceLock<()> = OnceLock::new();
    INIT.get_or_init(|| {
        let filter = tracing_subscriber::EnvFilter::try_from_default_env()
            .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
        // try_init returns Err if a subscriber is already set; that's
        // fine, we just want at-most-one to win.
        let _ = tracing_subscriber::fmt()
            .with_env_filter(filter)
            .with_writer(std::io::stderr)
            .with_target(false)
            .try_init();
    });
}

pub mod verify;
pub mod multi;
pub mod scheduler;
pub mod browser;
pub mod secrets;
pub mod permissions;
pub mod accounts;
pub mod integrations;
pub mod health;
pub mod voice;
pub mod meeting;
pub mod registry;
pub mod workflow;
pub mod a2a;
pub mod automation;
pub mod proxy;
pub mod vision;

/// Serialize an InferenceResult into the standard JSON format for FFI consumers.
pub fn tracked_result_to_json(result: &car_inference::InferenceResult) -> String {
    serde_json::json!({
        "text": result.text,
        "tool_calls": result.tool_calls,
        "model_used": result.model_used,
        "latency_ms": result.latency_ms,
        // Always emit so callers can distinguish "wasn't measured"
        // from "field doesn't exist on this binding's protocol
        // version". Populated by local Candle/MLX paths; null for
        // non-streaming remote calls.
        "time_to_first_token_ms": result.time_to_first_token_ms,
        "trace_id": result.trace_id,
        "usage": result.usage,
    })
    .to_string()
}