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