1 // Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
2 // SPDX-License-Identifier: MIT OR Apache-2.0
3
4 //! C FFI bridge for the Onde React Native npm SDK.
5 //!
6 //! This crate wraps [`onde::inference::engine::ChatEngine`] with `extern "C"`
7 //! functions callable from Swift (iOS) and Kotlin/JNI (Android).
8 //!
9 //! ## Design
10 //!
11 //! - **Opaque pointer** (`*mut c_void`) for the engine handle.
12 //! - **JSON strings** (via `serde_json`) for complex types crossing FFI.
13 //! - **Global `tokio::Runtime`** created once via `OnceLock`.
14 //! - Every function that returns a `*mut c_char` requires the caller to free
15 //! it with [`onde_free_string`].
16 //! - Android JNI wrappers are gated behind `#[cfg(target_os = "android")]`.
17
18 use std::ffi::{c_char, c_void, CStr, CString};
19 use std::sync::OnceLock;
20
21 use serde::de::DeserializeOwned;
22 use serde::Serialize;
23 use tokio::runtime::Runtime;
24
25 use onde::inference::engine::ChatEngine;
26 use onde::inference::types::{ChatMessage, GgufModelConfig, SamplingConfig};
27
28 // ── Global Tokio runtime ─────────────────────────────────────────────────────
29
30 /// Returns a reference to the global multi-threaded Tokio runtime.
31 ///
32 /// The runtime is created on first access and lives for the process lifetime.
33 fn runtime() -> &'static Runtime {
34 static RT: OnceLock<Runtime> = OnceLock::new();
35 RT.get_or_init(|| {
36 tokio::runtime::Builder::new_multi_thread()
37 .enable_all()
38 .build()
39 .expect("onde-react-native: failed to create tokio runtime")
40 })
41 }
42
43 // ── Helper functions ─────────────────────────────────────────────────────────
44
45 /// Serialize any `Serialize` value to a heap-allocated C string.
46 ///
47 /// The caller **must** free the returned pointer with [`onde_free_string`].
48 fn to_json_cstring<T: Serialize>(value: &T) -> *mut c_char {
49 match serde_json::to_string(value) {
50 Ok(json) => CString::new(json).unwrap_or_default().into_raw(),
51 Err(err) => error_json(&format!("JSON serialization failed: {err}")),
52 }
53 }
54
55 /// Deserialize a JSON C string into a Rust type.
56 ///
57 /// Returns `None` if the pointer is null or the JSON is malformed.
58 fn from_json_cstr<T: DeserializeOwned>(ptr: *const c_char) -> Option<T> {
59 if ptr.is_null() {
60 return None;
61 }
62 let cstr = unsafe { CStr::from_ptr(ptr) };
63 let s = cstr.to_str().ok()?;
64 serde_json::from_str(s).ok()
65 }
66
67 /// Read an optional C string parameter into an `Option<String>`.
68 ///
69 /// Returns `None` if the pointer is null or the string is empty.
70 fn nullable_str(ptr: *const c_char) -> Option<String> {
71 if ptr.is_null() {
72 return None;
73 }
74 let cstr = unsafe { CStr::from_ptr(ptr) };
75 let s = cstr.to_str().ok()?;
76 if s.is_empty() {
77 None
78 } else {
79 Some(s.to_owned())
80 }
81 }
82
83 /// Create a heap-allocated JSON error string: `{ "error": "<msg>" }`.
84 fn error_json(msg: &str) -> *mut c_char {
85 let json = serde_json::json!({ "error": msg });
86 CString::new(json.to_string())
87 .unwrap_or_default()
88 .into_raw()
89 }
90
91 // ── Engine lifecycle ─────────────────────────────────────────────────────────
92
93 /// Create a new `ChatEngine` and return an opaque pointer to it.
94 ///
95 /// The engine starts with no model loaded. The caller must eventually call
96 /// [`onde_engine_destroy`] to free the memory.
97 #[no_mangle]
98 pub extern "C" fn onde_engine_create() -> *mut c_void {
99 let engine = Box::new(ChatEngine::new());
100 Box::into_raw(engine) as *mut c_void
101 }
102
103 /// Destroy an engine previously created with [`onde_engine_create`].
104 ///
105 /// After this call the pointer is invalid and must not be used.
106 /// Passing a null pointer is a no-op.
107 #[no_mangle]
108 pub extern "C" fn onde_engine_destroy(engine: *mut c_void) {
109 if engine.is_null() {
110 return;
111 }
112 unsafe {
113 let _ = Box::from_raw(engine as *mut ChatEngine);
114 }
115 }
116
117 /// Load the platform-default GGUF model.
118 ///
119 /// # Parameters
120 ///
121 /// - `engine` — opaque engine pointer from [`onde_engine_create`].
122 /// - `system_prompt` — optional C string (pass null to omit).
123 /// - `sampling_json` — optional JSON-encoded `SamplingConfig` (pass null for defaults).
124 ///
125 /// # Returns
126 ///
127 /// A heap-allocated JSON C string (free with [`onde_free_string`]):
128 /// - On success: `{ "elapsed_secs": <f64> }`
129 /// - On failure: `{ "error": "<description>" }`
130 #[no_mangle]
131 pub extern "C" fn onde_engine_load_default_model(
132 engine: *mut c_void,
133 system_prompt: *const c_char,
134 sampling_json: *const c_char,
135 ) -> *mut c_char {
136 if engine.is_null() {
137 return error_json("engine pointer is null");
138 }
139 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
140 let prompt = nullable_str(system_prompt);
141 let sampling: Option<SamplingConfig> = from_json_cstr(sampling_json);
142 let config = GgufModelConfig::platform_default();
143
144 runtime().block_on(async {
145 match engine_ref.load_gguf_model(config, prompt, sampling).await {
146 Ok(duration) => {
147 let result = serde_json::json!({ "elapsed_secs": duration.as_secs_f64() });
148 to_json_cstring(&result)
149 }
150 Err(err) => error_json(&err.to_string()),
151 }
152 })
153 }
154
155 /// Load a specific GGUF model from a JSON-encoded [`GgufModelConfig`].
156 ///
157 /// # Parameters
158 ///
159 /// - `config_json` — JSON-encoded `GgufModelConfig`.
160 /// - `system_prompt` — optional C string (pass null to omit).
161 /// - `sampling_json` — optional JSON-encoded `SamplingConfig` (pass null for defaults).
162 ///
163 /// # Returns
164 ///
165 /// Same JSON shape as [`onde_engine_load_default_model`].
166 #[no_mangle]
167 pub extern "C" fn onde_engine_load_model(
168 engine: *mut c_void,
169 config_json: *const c_char,
170 system_prompt: *const c_char,
171 sampling_json: *const c_char,
172 ) -> *mut c_char {
173 if engine.is_null() {
174 return error_json("engine pointer is null");
175 }
176 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
177
178 let config: GgufModelConfig = match from_json_cstr(config_json) {
179 Some(c) => c,
180 None => return error_json("invalid or null config_json"),
181 };
182 let prompt = nullable_str(system_prompt);
183 let sampling: Option<SamplingConfig> = from_json_cstr(sampling_json);
184
185 runtime().block_on(async {
186 match engine_ref.load_gguf_model(config, prompt, sampling).await {
187 Ok(duration) => {
188 let result = serde_json::json!({ "elapsed_secs": duration.as_secs_f64() });
189 to_json_cstring(&result)
190 }
191 Err(err) => error_json(&err.to_string()),
192 }
193 })
194 }
195
196 /// Unload the currently loaded model.
197 ///
198 /// # Returns
199 ///
200 /// JSON: `{ "model_name": "<name>" }` or `{ "model_name": null }`.
201 #[no_mangle]
202 pub extern "C" fn onde_engine_unload_model(engine: *mut c_void) -> *mut c_char {
203 if engine.is_null() {
204 return error_json("engine pointer is null");
205 }
206 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
207 let name = runtime().block_on(engine_ref.unload_model());
208 let result = serde_json::json!({ "model_name": name });
209 to_json_cstring(&result)
210 }
211
212 /// Check whether a model is currently loaded.
213 #[no_mangle]
214 pub extern "C" fn onde_engine_is_loaded(engine: *mut c_void) -> bool {
215 if engine.is_null() {
216 return false;
217 }
218 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
219 runtime().block_on(engine_ref.is_loaded())
220 }
221
222 // ── Engine info ──────────────────────────────────────────────────────────────
223
224 /// Get a snapshot of the engine's current state as JSON.
225 ///
226 /// # Returns
227 ///
228 /// JSON-encoded [`EngineInfo`](onde::inference::types::EngineInfo).
229 #[no_mangle]
230 pub extern "C" fn onde_engine_info(engine: *mut c_void) -> *mut c_char {
231 if engine.is_null() {
232 return error_json("engine pointer is null");
233 }
234 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
235 let info = runtime().block_on(engine_ref.info());
236 to_json_cstring(&info)
237 }
238
239 // ── System prompt ────────────────────────────────────────────────────────────
240
241 /// Set or replace the engine's system prompt.
242 #[no_mangle]
243 pub extern "C" fn onde_engine_set_system_prompt(engine: *mut c_void, prompt: *const c_char) {
244 if engine.is_null() {
245 return;
246 }
247 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
248 if let Some(text) = nullable_str(prompt) {
249 runtime().block_on(engine_ref.set_system_prompt(text));
250 }
251 }
252
253 /// Clear the engine's system prompt.
254 #[no_mangle]
255 pub extern "C" fn onde_engine_clear_system_prompt(engine: *mut c_void) {
256 if engine.is_null() {
257 return;
258 }
259 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
260 runtime().block_on(engine_ref.clear_system_prompt());
261 }
262
263 // ── Sampling ─────────────────────────────────────────────────────────────────
264
265 /// Replace the engine's sampling configuration from a JSON string.
266 #[no_mangle]
267 pub extern "C" fn onde_engine_set_sampling(engine: *mut c_void, sampling_json: *const c_char) {
268 if engine.is_null() {
269 return;
270 }
271 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
272 if let Some(sampling) = from_json_cstr::<SamplingConfig>(sampling_json) {
273 runtime().block_on(engine_ref.set_sampling(sampling));
274 }
275 }
276
277 // ── History ──────────────────────────────────────────────────────────────────
278
279 /// Get the conversation history as a JSON array of `ChatMessage` objects.
280 #[no_mangle]
281 pub extern "C" fn onde_engine_history(engine: *mut c_void) -> *mut c_char {
282 if engine.is_null() {
283 return to_json_cstring(&serde_json::json!([]));
284 }
285 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
286 let history = runtime().block_on(engine_ref.history());
287 to_json_cstring(&history)
288 }
289
290 /// Clear the conversation history. Returns the number of turns removed.
291 #[no_mangle]
292 pub extern "C" fn onde_engine_clear_history(engine: *mut c_void) -> u64 {
293 if engine.is_null() {
294 return 0;
295 }
296 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
297 runtime().block_on(engine_ref.clear_history()) as u64
298 }
299
300 /// Append a message to the conversation history without running inference.
301 ///
302 /// `message_json` must be a JSON-encoded `ChatMessage`.
303 #[no_mangle]
304 pub extern "C" fn onde_engine_push_history(engine: *mut c_void, message_json: *const c_char) {
305 if engine.is_null() {
306 return;
307 }
308 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
309 if let Some(message) = from_json_cstr::<ChatMessage>(message_json) {
310 runtime().block_on(engine_ref.push_history(message));
311 }
312 }
313
314 // ── Non-streaming inference ──────────────────────────────────────────────────
315
316 /// Send a user message and receive a complete assistant reply.
317 ///
318 /// The message and reply are appended to the conversation history.
319 ///
320 /// # Returns
321 ///
322 /// JSON-encoded [`InferenceResult`](onde::inference::types::InferenceResult)
323 /// on success, or `{ "error": "<description>" }` on failure.
324 #[no_mangle]
325 pub extern "C" fn onde_engine_send_message(
326 engine: *mut c_void,
327 message: *const c_char,
328 ) -> *mut c_char {
329 if engine.is_null() {
330 return error_json("engine pointer is null");
331 }
332 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
333 let text = match nullable_str(message) {
334 Some(t) => t,
335 None => return error_json("message is null or empty"),
336 };
337
338 runtime().block_on(async {
339 match engine_ref.send_message(text).await {
340 Ok(result) => to_json_cstring(&result),
341 Err(err) => error_json(&err.to_string()),
342 }
343 })
344 }
345
346 /// One-shot generation without modifying conversation history.
347 ///
348 /// # Parameters
349 ///
350 /// - `messages_json` — JSON array of `ChatMessage`.
351 /// - `sampling_json` — optional JSON-encoded `SamplingConfig` (null for engine defaults).
352 ///
353 /// # Returns
354 ///
355 /// JSON-encoded [`InferenceResult`] or `{ "error": "..." }`.
356 #[no_mangle]
357 pub extern "C" fn onde_engine_generate(
358 engine: *mut c_void,
359 messages_json: *const c_char,
360 sampling_json: *const c_char,
361 ) -> *mut c_char {
362 if engine.is_null() {
363 return error_json("engine pointer is null");
364 }
365 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
366
367 let messages: Vec<ChatMessage> = match from_json_cstr(messages_json) {
368 Some(m) => m,
369 None => return error_json("invalid or null messages_json"),
370 };
371 let sampling: Option<SamplingConfig> = from_json_cstr(sampling_json);
372
373 runtime().block_on(async {
374 match engine_ref.generate(messages, sampling).await {
375 Ok(result) => to_json_cstring(&result),
376 Err(err) => error_json(&err.to_string()),
377 }
378 })
379 }
380
381 // ── Streaming inference ──────────────────────────────────────────────────────
382
383 /// Send a user message and stream token chunks via a C callback.
384 ///
385 /// The callback is invoked for each chunk with:
386 /// - `chunk_json` — a JSON-encoded [`StreamChunk`](onde::inference::types::StreamChunk)
387 /// (valid only for the duration of the callback invocation).
388 /// - `done` — `true` when this is the final chunk.
389 ///
390 /// # Returns
391 ///
392 /// - `null` on success (streaming completed).
393 /// - A heap-allocated JSON error string on failure (free with [`onde_free_string`]).
394 #[no_mangle]
395 pub extern "C" fn onde_engine_stream_message(
396 engine: *mut c_void,
397 message: *const c_char,
398 callback: extern "C" fn(*const c_char, bool),
399 ) -> *mut c_char {
400 if engine.is_null() {
401 return error_json("engine pointer is null");
402 }
403 let engine_ref = unsafe { &*(engine as *const ChatEngine) };
404 let text = match nullable_str(message) {
405 Some(t) => t,
406 None => return error_json("message is null or empty"),
407 };
408
409 runtime().block_on(async {
410 let receiver = match engine_ref.stream_message(text).await {
411 Ok(rx) => rx,
412 Err(err) => return error_json(&err.to_string()),
413 };
414
415 // Drain the channel and forward each chunk through the callback.
416 let mut receiver = receiver;
417 loop {
418 match receiver.recv().await {
419 Some(chunk) => {
420 let done = chunk.done;
421 match serde_json::to_string(&chunk) {
422 Ok(json) => {
423 if let Ok(cstr) = CString::new(json) {
424 callback(cstr.as_ptr(), done);
425 }
426 }
427 Err(err) => {
428 log::error!(
429 "onde_engine_stream_message: JSON serialization failed: {err}"
430 );
431 }
432 }
433 if done {
434 break;
435 }
436 }
437 None => {
438 // Channel closed without a done chunk — send a synthetic one.
439 let final_json = serde_json::json!({
440 "delta": "",
441 "done": true,
442 "finish_reason": "channel_closed"
443 });
444 if let Ok(cstr) = CString::new(final_json.to_string()) {
445 callback(cstr.as_ptr(), true);
446 }
447 break;
448 }
449 }
450 }
451
452 std::ptr::null_mut()
453 })
454 }
455
456 // ── Model config presets ─────────────────────────────────────────────────────
457
458 /// Return the platform-default GGUF model config as JSON.
459 #[no_mangle]
460 pub extern "C" fn onde_default_model_config() -> *mut c_char {
461 to_json_cstring(&GgufModelConfig::platform_default())
462 }
463
464 /// Return the Qwen 2.5 1.5B GGUF model config as JSON.
465 #[no_mangle]
466 pub extern "C" fn onde_qwen25_1_5b_config() -> *mut c_char {
467 to_json_cstring(&GgufModelConfig::qwen25_1_5b())
468 }
469
470 /// Return the Qwen 2.5 3B GGUF model config as JSON.
471 #[no_mangle]
472 pub extern "C" fn onde_qwen25_3b_config() -> *mut c_char {
473 to_json_cstring(&GgufModelConfig::qwen25_3b())
474 }
475
476 // ── Qwen 3 family ────────────────────────────────────────────────────────────
477
478 /// Return the Qwen 3 0.6B GGUF model config as JSON.
479 #[no_mangle]
480 pub extern "C" fn onde_qwen3_0_6b_config() -> *mut c_char {
481 to_json_cstring(&GgufModelConfig::qwen3_0_6b())
482 }
483
484 /// Return the Qwen 3 1.7B GGUF model config as JSON.
485 #[no_mangle]
486 pub extern "C" fn onde_qwen3_1_7b_config() -> *mut c_char {
487 to_json_cstring(&GgufModelConfig::qwen3_1_7b())
488 }
489
490 /// Return the Qwen 3 4B GGUF model config as JSON.
491 #[no_mangle]
492 pub extern "C" fn onde_qwen3_4b_config() -> *mut c_char {
493 to_json_cstring(&GgufModelConfig::qwen3_4b())
494 }
495
496 /// Return the Qwen 3 8B GGUF model config as JSON.
497 #[no_mangle]
498 pub extern "C" fn onde_qwen3_8b_config() -> *mut c_char {
499 to_json_cstring(&GgufModelConfig::qwen3_8b())
500 }
501
502 /// Return the Qwen 3 14B GGUF model config as JSON.
503 #[no_mangle]
504 pub extern "C" fn onde_qwen3_14b_config() -> *mut c_char {
505 to_json_cstring(&GgufModelConfig::qwen3_14b())
506 }
507
508 /// Return the Qwen 3 32B GGUF model config as JSON.
509 #[no_mangle]
510 pub extern "C" fn onde_qwen3_32b_config() -> *mut c_char {
511 to_json_cstring(&GgufModelConfig::qwen3_32b())
512 }
513
514 /// Return the Qwen 3 4B Instruct 2507 GGUF model config as JSON.
515 #[no_mangle]
516 pub extern "C" fn onde_qwen3_4b_instruct_2507_config() -> *mut c_char {
517 to_json_cstring(&GgufModelConfig::qwen3_4b_instruct_2507())
518 }
519
520 /// Return the Qwen 3 4B Thinking 2507 GGUF model config as JSON.
521 #[no_mangle]
522 pub extern "C" fn onde_qwen3_4b_thinking_2507_config() -> *mut c_char {
523 to_json_cstring(&GgufModelConfig::qwen3_4b_thinking_2507())
524 }
525
526 /// Return the Qwen 3 30B-A3B Instruct 2507 (MoE) GGUF model config as JSON.
527 #[no_mangle]
528 pub extern "C" fn onde_qwen3_30b_a3b_instruct_2507_config() -> *mut c_char {
529 to_json_cstring(&GgufModelConfig::qwen3_30b_a3b_instruct_2507())
530 }
531
532 // ── Sampling presets ─────────────────────────────────────────────────────────
533
534 /// Return the default sampling config as JSON.
535 #[no_mangle]
536 pub extern "C" fn onde_default_sampling_config() -> *mut c_char {
537 to_json_cstring(&SamplingConfig::default())
538 }
539
540 /// Return the deterministic (greedy) sampling config as JSON.
541 #[no_mangle]
542 pub extern "C" fn onde_deterministic_sampling_config() -> *mut c_char {
543 to_json_cstring(&SamplingConfig::deterministic())
544 }
545
546 /// Return the mobile sampling config as JSON.
547 #[no_mangle]
548 pub extern "C" fn onde_mobile_sampling_config() -> *mut c_char {
549 to_json_cstring(&SamplingConfig::mobile())
550 }
551
552 // ── Memory management ────────────────────────────────────────────────────────
553
554 /// Free a C string previously returned by any `onde_*` function.
555 ///
556 /// Passing a null pointer is a no-op.
557 #[no_mangle]
558 #[allow(clippy::not_unsafe_ptr_arg_deref)]
559 pub extern "C" fn onde_free_string(ptr: *mut c_char) {
560 if ptr.is_null() {
561 return;
562 }
563 unsafe {
564 let _ = CString::from_raw(ptr);
565 }
566 }
567
568 // ═══════════════════════════════════════════════════════════════════════════════
569 // Android JNI bindings
570 // ═══════════════════════════════════════════════════════════════════════════════
571
572 #[cfg(target_os = "android")]
573 mod android {
574 use jni::objects::{JClass, JString};
575 use jni::sys::{jboolean, jlong, jstring, JNI_FALSE, JNI_TRUE};
576 use jni::JNIEnv;
577
578 use std::ffi::{c_char, c_void, CStr, CString};
579
580 use super::*;
581
582 // ── JNI helpers ──────────────────────────────────────────────────────
583
584 /// Convert a JNI `JString` to a `CString`.
585 ///
586 /// Returns `None` if the JString is null or conversion fails.
587 /// The caller must keep the returned `CString` alive for as long as
588 /// the `.as_ptr()` result is used.
589 fn jstring_to_cstring(env: &mut JNIEnv, jstr: &JString) -> Option<CString> {
590 if jstr.is_null() {
591 return None;
592 }
593 let java_str = env.get_string(jstr).ok()?;
594 let rust_str: String = java_str.into();
595 CString::new(rust_str).ok()
596 }
597
598 /// Convert a `*mut c_char` (owned C string) to a JNI `jstring`.
599 ///
600 /// Frees the C string after conversion. Returns null jstring on failure.
601 fn cstring_ptr_to_jstring(env: &mut JNIEnv, ptr: *mut c_char) -> jstring {
602 if ptr.is_null() {
603 return std::ptr::null_mut();
604 }
605 let cstr = unsafe { CStr::from_ptr(ptr) };
606 let result = match cstr.to_str() {
607 Ok(s) => env
608 .new_string(s)
609 .map(|js| js.into_raw())
610 .unwrap_or(std::ptr::null_mut()),
611 Err(_) => std::ptr::null_mut(),
612 };
613 // Free the C string.
614 onde_free_string(ptr);
615 result
616 }
617
618 // ── Engine lifecycle ─────────────────────────────────────────────────
619
620 #[no_mangle]
621 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineCreate(
622 _env: JNIEnv,
623 _class: JClass,
624 ) -> jlong {
625 onde_engine_create() as jlong
626 }
627
628 #[no_mangle]
629 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineDestroy(
630 _env: JNIEnv,
631 _class: JClass,
632 engine: jlong,
633 ) {
634 onde_engine_destroy(engine as *mut c_void);
635 }
636
637 #[no_mangle]
638 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineLoadDefaultModel(
639 mut env: JNIEnv,
640 _class: JClass,
641 engine: jlong,
642 system_prompt: JString,
643 sampling_json: JString,
644 ) -> jstring {
645 let prompt_cstr = jstring_to_cstring(&mut env, &system_prompt);
646 let sampling_cstr = jstring_to_cstring(&mut env, &sampling_json);
647
648 let prompt_ptr = prompt_cstr
649 .as_ref()
650 .map(|c| c.as_ptr())
651 .unwrap_or(std::ptr::null());
652 let sampling_ptr = sampling_cstr
653 .as_ref()
654 .map(|c| c.as_ptr())
655 .unwrap_or(std::ptr::null());
656
657 let result =
658 onde_engine_load_default_model(engine as *mut c_void, prompt_ptr, sampling_ptr);
659 cstring_ptr_to_jstring(&mut env, result)
660 }
661
662 #[no_mangle]
663 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineLoadModel(
664 mut env: JNIEnv,
665 _class: JClass,
666 engine: jlong,
667 config_json: JString,
668 system_prompt: JString,
669 sampling_json: JString,
670 ) -> jstring {
671 let config_cstr = jstring_to_cstring(&mut env, &config_json);
672 let prompt_cstr = jstring_to_cstring(&mut env, &system_prompt);
673 let sampling_cstr = jstring_to_cstring(&mut env, &sampling_json);
674
675 let config_ptr = config_cstr
676 .as_ref()
677 .map(|c| c.as_ptr())
678 .unwrap_or(std::ptr::null());
679 let prompt_ptr = prompt_cstr
680 .as_ref()
681 .map(|c| c.as_ptr())
682 .unwrap_or(std::ptr::null());
683 let sampling_ptr = sampling_cstr
684 .as_ref()
685 .map(|c| c.as_ptr())
686 .unwrap_or(std::ptr::null());
687
688 let result =
689 onde_engine_load_model(engine as *mut c_void, config_ptr, prompt_ptr, sampling_ptr);
690 cstring_ptr_to_jstring(&mut env, result)
691 }
692
693 #[no_mangle]
694 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineUnloadModel(
695 mut env: JNIEnv,
696 _class: JClass,
697 engine: jlong,
698 ) -> jstring {
699 let result = onde_engine_unload_model(engine as *mut c_void);
700 cstring_ptr_to_jstring(&mut env, result)
701 }
702
703 #[no_mangle]
704 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineIsLoaded(
705 _env: JNIEnv,
706 _class: JClass,
707 engine: jlong,
708 ) -> jboolean {
709 if onde_engine_is_loaded(engine as *mut c_void) {
710 JNI_TRUE
711 } else {
712 JNI_FALSE
713 }
714 }
715
716 // ── Engine info ──────────────────────────────────────────────────────
717
718 #[no_mangle]
719 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineInfo(
720 mut env: JNIEnv,
721 _class: JClass,
722 engine: jlong,
723 ) -> jstring {
724 let result = onde_engine_info(engine as *mut c_void);
725 cstring_ptr_to_jstring(&mut env, result)
726 }
727
728 // ── System prompt ────────────────────────────────────────────────────
729
730 #[no_mangle]
731 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineSetSystemPrompt(
732 mut env: JNIEnv,
733 _class: JClass,
734 engine: jlong,
735 prompt: JString,
736 ) {
737 let prompt_cstr = jstring_to_cstring(&mut env, &prompt);
738 let prompt_ptr = prompt_cstr
739 .as_ref()
740 .map(|c| c.as_ptr())
741 .unwrap_or(std::ptr::null());
742 onde_engine_set_system_prompt(engine as *mut c_void, prompt_ptr);
743 }
744
745 #[no_mangle]
746 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineClearSystemPrompt(
747 _env: JNIEnv,
748 _class: JClass,
749 engine: jlong,
750 ) {
751 onde_engine_clear_system_prompt(engine as *mut c_void);
752 }
753
754 // ── Sampling ─────────────────────────────────────────────────────────
755
756 #[no_mangle]
757 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineSetSampling(
758 mut env: JNIEnv,
759 _class: JClass,
760 engine: jlong,
761 sampling_json: JString,
762 ) {
763 let sampling_cstr = jstring_to_cstring(&mut env, &sampling_json);
764 let sampling_ptr = sampling_cstr
765 .as_ref()
766 .map(|c| c.as_ptr())
767 .unwrap_or(std::ptr::null());
768 onde_engine_set_sampling(engine as *mut c_void, sampling_ptr);
769 }
770
771 // ── History ──────────────────────────────────────────────────────────
772
773 #[no_mangle]
774 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineHistory(
775 mut env: JNIEnv,
776 _class: JClass,
777 engine: jlong,
778 ) -> jstring {
779 let result = onde_engine_history(engine as *mut c_void);
780 cstring_ptr_to_jstring(&mut env, result)
781 }
782
783 #[no_mangle]
784 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineClearHistory(
785 _env: JNIEnv,
786 _class: JClass,
787 engine: jlong,
788 ) -> jlong {
789 onde_engine_clear_history(engine as *mut c_void) as jlong
790 }
791
792 #[no_mangle]
793 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEnginePushHistory(
794 mut env: JNIEnv,
795 _class: JClass,
796 engine: jlong,
797 message_json: JString,
798 ) {
799 let msg_cstr = jstring_to_cstring(&mut env, &message_json);
800 let msg_ptr = msg_cstr
801 .as_ref()
802 .map(|c| c.as_ptr())
803 .unwrap_or(std::ptr::null());
804 onde_engine_push_history(engine as *mut c_void, msg_ptr);
805 }
806
807 // ── Inference ────────────────────────────────────────────────────────
808
809 #[no_mangle]
810 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineSendMessage(
811 mut env: JNIEnv,
812 _class: JClass,
813 engine: jlong,
814 message: JString,
815 ) -> jstring {
816 let msg_cstr = jstring_to_cstring(&mut env, &message);
817 let msg_ptr = msg_cstr
818 .as_ref()
819 .map(|c| c.as_ptr())
820 .unwrap_or(std::ptr::null());
821 let result = onde_engine_send_message(engine as *mut c_void, msg_ptr);
822 cstring_ptr_to_jstring(&mut env, result)
823 }
824
825 #[no_mangle]
826 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineGenerate(
827 mut env: JNIEnv,
828 _class: JClass,
829 engine: jlong,
830 messages_json: JString,
831 sampling_json: JString,
832 ) -> jstring {
833 let msgs_cstr = jstring_to_cstring(&mut env, &messages_json);
834 let sampling_cstr = jstring_to_cstring(&mut env, &sampling_json);
835
836 let msgs_ptr = msgs_cstr
837 .as_ref()
838 .map(|c| c.as_ptr())
839 .unwrap_or(std::ptr::null());
840 let sampling_ptr = sampling_cstr
841 .as_ref()
842 .map(|c| c.as_ptr())
843 .unwrap_or(std::ptr::null());
844
845 let result = onde_engine_generate(engine as *mut c_void, msgs_ptr, sampling_ptr);
846 cstring_ptr_to_jstring(&mut env, result)
847 }
848
849 // ── Model config presets ─────────────────────────────────────────────
850
851 #[no_mangle]
852 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeDefaultModelConfig(
853 mut env: JNIEnv,
854 _class: JClass,
855 ) -> jstring {
856 let result = onde_default_model_config();
857 cstring_ptr_to_jstring(&mut env, result)
858 }
859
860 #[no_mangle]
861 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen251_15bConfig(
862 mut env: JNIEnv,
863 _class: JClass,
864 ) -> jstring {
865 let result = onde_qwen25_1_5b_config();
866 cstring_ptr_to_jstring(&mut env, result)
867 }
868
869 #[no_mangle]
870 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen253bConfig(
871 mut env: JNIEnv,
872 _class: JClass,
873 ) -> jstring {
874 let result = onde_qwen25_3b_config();
875 cstring_ptr_to_jstring(&mut env, result)
876 }
877
878 #[no_mangle]
879 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_10_16bConfig(
880 mut env: JNIEnv,
881 _class: JClass,
882 ) -> jstring {
883 cstring_ptr_to_jstring(&mut env, onde_qwen3_0_6b_config())
884 }
885
886 #[no_mangle]
887 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_11_17bConfig(
888 mut env: JNIEnv,
889 _class: JClass,
890 ) -> jstring {
891 cstring_ptr_to_jstring(&mut env, onde_qwen3_1_7b_config())
892 }
893
894 #[no_mangle]
895 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_14bConfig(
896 mut env: JNIEnv,
897 _class: JClass,
898 ) -> jstring {
899 cstring_ptr_to_jstring(&mut env, onde_qwen3_4b_config())
900 }
901
902 #[no_mangle]
903 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_18bConfig(
904 mut env: JNIEnv,
905 _class: JClass,
906 ) -> jstring {
907 cstring_ptr_to_jstring(&mut env, onde_qwen3_8b_config())
908 }
909
910 #[no_mangle]
911 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_114bConfig(
912 mut env: JNIEnv,
913 _class: JClass,
914 ) -> jstring {
915 cstring_ptr_to_jstring(&mut env, onde_qwen3_14b_config())
916 }
917
918 #[no_mangle]
919 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_132bConfig(
920 mut env: JNIEnv,
921 _class: JClass,
922 ) -> jstring {
923 cstring_ptr_to_jstring(&mut env, onde_qwen3_32b_config())
924 }
925
926 #[no_mangle]
927 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_14bInstruct2507Config(
928 mut env: JNIEnv,
929 _class: JClass,
930 ) -> jstring {
931 cstring_ptr_to_jstring(&mut env, onde_qwen3_4b_instruct_2507_config())
932 }
933
934 #[no_mangle]
935 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_14bThinking2507Config(
936 mut env: JNIEnv,
937 _class: JClass,
938 ) -> jstring {
939 cstring_ptr_to_jstring(&mut env, onde_qwen3_4b_thinking_2507_config())
940 }
941
942 #[no_mangle]
943 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_130bA3bInstruct2507Config(
944 mut env: JNIEnv,
945 _class: JClass,
946 ) -> jstring {
947 cstring_ptr_to_jstring(&mut env, onde_qwen3_30b_a3b_instruct_2507_config())
948 }
949
950 // ── Sampling presets ─────────────────────────────────────────────────
951
952 #[no_mangle]
953 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeDefaultSamplingConfig(
954 mut env: JNIEnv,
955 _class: JClass,
956 ) -> jstring {
957 let result = onde_default_sampling_config();
958 cstring_ptr_to_jstring(&mut env, result)
959 }
960
961 #[no_mangle]
962 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeDeterministicSamplingConfig(
963 mut env: JNIEnv,
964 _class: JClass,
965 ) -> jstring {
966 let result = onde_deterministic_sampling_config();
967 cstring_ptr_to_jstring(&mut env, result)
968 }
969
970 #[no_mangle]
971 pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeMobileSamplingConfig(
972 mut env: JNIEnv,
973 _class: JClass,
974 ) -> jstring {
975 let result = onde_mobile_sampling_config();
976 cstring_ptr_to_jstring(&mut env, result)
977 }
978 }