baml_sys/
lib.rs

1//! BAML FFI bindings with runtime dynamic library loading.
2//!
3//! This crate provides low-level FFI bindings to the BAML runtime library.
4//! The library is loaded dynamically at runtime using `libloading`.
5//!
6//! # Library Resolution
7//!
8//! The library is searched in the following order:
9//! 1. Explicit path set via [`set_library_path()`]
10//! 2. `BAML_LIBRARY_PATH` environment variable
11//! 3. User cache directory (`~/.cache/baml/libs/{VERSION}/`)
12//! 4. Auto-download from GitHub releases (if `download` feature enabled)
13//! 5. System default paths (`/usr/local/lib/`, etc.)
14//!
15//! # Usage
16//!
17//! ## Runtime Loading (Default)
18//!
19//! ```rust,ignore
20//! use baml_sys::{get_symbols, version};
21//!
22//! // Library is loaded on first access
23//! let v = version()?;
24//! println!("BAML version: {v}");
25//! ```
26//!
27//! ## Build-time Hook
28//!
29//! In your `build.rs`:
30//!
31//! ```rust,ignore
32//! fn main() {
33//!     // Ensure library is available before build completes
34//!     let lib_path = baml_sys::ensure_library()
35//!         .expect("Failed to find/download BAML library");
36//!     println!("cargo:rerun-if-changed={}", lib_path.display());
37//! }
38//! ```
39//!
40//! # Environment Variables
41//!
42//! - `BAML_LIBRARY_PATH`: Explicit path to the library
43//! - `BAML_CACHE_DIR`: Override cache directory location
44//! - `BAML_LIBRARY_DISABLE_DOWNLOAD`: Set to "true" to disable auto-download
45
46#![warn(missing_docs)]
47
48mod download;
49mod error;
50mod loader;
51mod symbols;
52
53pub use error::{BamlSysError, Result};
54use libc::{c_char, c_int, c_void, size_t};
55pub use loader::{
56    ensure_library, set_library_path, ENV_CACHE_DIR, ENV_DISABLE_DOWNLOAD, ENV_LIBRARY_PATH,
57    VERSION,
58};
59pub use symbols::{get_symbols, Buffer, CallbackFn, OnTickCallbackFn, Symbols};
60
61// ============================================================================
62// Safe wrapper functions
63// ============================================================================
64
65/// Get the BAML library version.
66pub fn version() -> Result<String> {
67    let symbols = get_symbols()?;
68    // Safety: version() returns a Buffer containing the version string
69    #[allow(unsafe_code)]
70    let buf = unsafe { (symbols.version)() };
71
72    let result = if !buf.ptr.is_null() && buf.len > 0 {
73        #[allow(unsafe_code)]
74        let bytes = unsafe { std::slice::from_raw_parts(buf.ptr as *const u8, buf.len) };
75        String::from_utf8_lossy(bytes).into_owned()
76    } else {
77        "unknown".to_string()
78    };
79
80    // Free the buffer
81    #[allow(unsafe_code)]
82    unsafe {
83        (symbols.free_buffer)(buf);
84    }
85
86    Ok(result)
87}
88
89/// Register callbacks for FFI operations.
90///
91/// # Safety
92/// The callbacks must remain valid for the lifetime of the program.
93#[allow(unsafe_code)]
94pub unsafe fn register_callbacks(
95    callback_fn: CallbackFn,
96    error_callback_fn: CallbackFn,
97    on_tick_callback_fn: OnTickCallbackFn,
98) -> Result<()> {
99    let symbols = get_symbols()?;
100    unsafe {
101        (symbols.register_callbacks)(callback_fn, error_callback_fn, on_tick_callback_fn);
102    }
103    Ok(())
104}
105
106/// Create a new BAML runtime.
107///
108/// # Safety
109/// All pointers must be valid C strings.
110#[allow(unsafe_code)]
111pub unsafe fn create_baml_runtime(
112    root_path: *const c_char,
113    src_files_json: *const c_char,
114    env_vars_json: *const c_char,
115) -> Result<*const c_void> {
116    let symbols = get_symbols()?;
117    Ok(unsafe { (symbols.create_baml_runtime)(root_path, src_files_json, env_vars_json) })
118}
119
120/// Destroy a BAML runtime.
121///
122/// # Safety
123/// The runtime pointer must be valid and not already destroyed.
124#[allow(unsafe_code)]
125pub unsafe fn destroy_baml_runtime(runtime: *const c_void) -> Result<()> {
126    let symbols = get_symbols()?;
127    unsafe {
128        (symbols.destroy_baml_runtime)(runtime);
129    }
130    Ok(())
131}
132
133/// Invoke the runtime CLI.
134///
135/// # Safety
136/// The args pointer must be a valid null-terminated array of C strings.
137#[allow(unsafe_code)]
138pub unsafe fn invoke_runtime_cli(args: *const *const c_char) -> Result<c_int> {
139    let symbols = get_symbols()?;
140    Ok(unsafe { (symbols.invoke_runtime_cli)(args) })
141}
142
143/// Call a BAML function.
144///
145/// Returns a Buffer containing the InvocationResponse protobuf.
146/// The caller is responsible for decoding the buffer and freeing it.
147///
148/// # Safety
149/// All pointers must be valid.
150#[allow(unsafe_code)]
151pub unsafe fn call_function_from_c(
152    runtime: *const c_void,
153    function_name: *const c_char,
154    encoded_args: *const c_char,
155    length: size_t,
156    id: u32,
157) -> Result<Buffer> {
158    let symbols = get_symbols()?;
159    Ok(unsafe { (symbols.call_function_from_c)(runtime, function_name, encoded_args, length, id) })
160}
161
162/// Call a BAML function with streaming.
163///
164/// Returns a Buffer containing the InvocationResponse protobuf.
165/// The caller is responsible for decoding the buffer and freeing it.
166///
167/// # Safety
168/// All pointers must be valid.
169#[allow(unsafe_code)]
170pub unsafe fn call_function_stream_from_c(
171    runtime: *const c_void,
172    function_name: *const c_char,
173    encoded_args: *const c_char,
174    length: size_t,
175    id: u32,
176) -> Result<Buffer> {
177    let symbols = get_symbols()?;
178    Ok(unsafe {
179        (symbols.call_function_stream_from_c)(runtime, function_name, encoded_args, length, id)
180    })
181}
182
183/// Call a BAML function for parsing.
184///
185/// Returns a Buffer containing the InvocationResponse protobuf.
186/// The caller is responsible for decoding the buffer and freeing it.
187///
188/// # Safety
189/// All pointers must be valid.
190#[allow(unsafe_code)]
191pub unsafe fn call_function_parse_from_c(
192    runtime: *const c_void,
193    function_name: *const c_char,
194    encoded_args: *const c_char,
195    length: size_t,
196    id: u32,
197) -> Result<Buffer> {
198    let symbols = get_symbols()?;
199    Ok(unsafe {
200        (symbols.call_function_parse_from_c)(runtime, function_name, encoded_args, length, id)
201    })
202}
203
204/// Cancel a function call.
205///
206/// Returns a Buffer containing the InvocationResponse protobuf.
207/// The caller is responsible for decoding the buffer and freeing it.
208///
209/// # Safety
210/// The id must be a valid call ID.
211#[allow(unsafe_code)]
212pub unsafe fn cancel_function_call(id: u32) -> Result<Buffer> {
213    let symbols = get_symbols()?;
214    Ok(unsafe { (symbols.cancel_function_call)(id) })
215}
216
217/// Call an object constructor.
218///
219/// # Safety
220/// The `encoded_invocation` pointer must be valid.
221#[allow(unsafe_code)]
222pub unsafe fn call_object_constructor(
223    encoded_invocation: *const c_char,
224    length: size_t,
225) -> Result<Buffer> {
226    let symbols = get_symbols()?;
227    Ok(unsafe { (symbols.call_object_constructor)(encoded_invocation, length) })
228}
229
230/// Call an object method.
231///
232/// # Safety
233/// All pointers must be valid.
234#[allow(unsafe_code)]
235pub unsafe fn call_object_method(
236    runtime: *const c_void,
237    encoded_invocation: *const c_char,
238    length: size_t,
239) -> Result<Buffer> {
240    let symbols = get_symbols()?;
241    Ok(unsafe { (symbols.call_object_method)(runtime, encoded_invocation, length) })
242}
243
244/// Free a buffer returned from object operations.
245///
246/// # Safety
247/// The buffer must have been returned from this library.
248#[allow(unsafe_code)]
249pub unsafe fn free_buffer(buf: Buffer) -> Result<()> {
250    let symbols = get_symbols()?;
251    unsafe {
252        (symbols.free_buffer)(buf);
253    }
254    Ok(())
255}
256
257/// Get the path to the loaded library.
258pub fn library_path() -> Result<std::path::PathBuf> {
259    let lib = loader::get_library()?;
260    Ok(lib.path.clone())
261}