#![allow(dead_code)]
#![allow(clippy::result_large_err)]
use std::ffi::{c_char, c_void};
use std::pin::Pin;
use std::sync::Arc;
use async_trait::async_trait;
use futures_util::stream::{self, Stream};
use blazen_llm::compute::{
AudioResult as InnerAudioResult, BackgroundRemovalRequest as InnerBackgroundRemovalRequest,
ImageRequest as InnerImageRequest, ImageResult as InnerImageResult,
MusicRequest as InnerMusicRequest, SpeechRequest as InnerSpeechRequest,
ThreeDRequest as InnerThreeDRequest, ThreeDResult as InnerThreeDResult,
TranscriptionRequest as InnerTranscriptionRequest,
TranscriptionResult as InnerTranscriptionResult, UpscaleRequest as InnerUpscaleRequest,
VideoRequest as InnerVideoRequest, VideoResult as InnerVideoResult,
VoiceCloneRequest as InnerVoiceCloneRequest, VoiceHandle as InnerVoiceHandle,
};
use blazen_llm::error::BlazenError as LlmError;
use blazen_llm::providers::base::BaseProvider as InnerBaseProvider;
use blazen_llm::providers::custom::{
self as inner_custom, CustomProvider as InnerCustomProviderTrait,
CustomProviderHandle as InnerCustomProviderHandle,
};
use blazen_llm::traits::CompletionModel;
use blazen_llm::types::{
CompletionRequest as LlmCompletionRequest, CompletionResponse as LlmCompletionResponse,
EmbeddingResponse as LlmEmbeddingResponse, StreamChunk as LlmStreamChunk,
};
use blazen_uniffi::errors::BlazenError as UniffiError;
use blazen_uniffi::llm::{
CompletionRequest as UniffiCompletionRequest, CompletionResponse as UniffiCompletionResponse,
EmbeddingResponse as UniffiEmbeddingResponse,
};
use blazen_uniffi::streaming::StreamChunk as UniffiStreamChunk;
use crate::compute_requests::{
BlazenBackgroundRemovalRequest, BlazenImageRequest, BlazenMusicRequest, BlazenSpeechRequest,
BlazenThreeDRequest, BlazenTranscriptionRequest, BlazenUpscaleRequest, BlazenVideoRequest,
BlazenVoiceCloneRequest,
};
use crate::compute_results::{
BlazenAudioResult, BlazenImageResult, BlazenThreeDResult, BlazenTranscriptionResult,
BlazenVideoResult, BlazenVoiceHandle,
};
use crate::error::BlazenError;
use crate::future::BlazenFuture;
use crate::llm_records::{
BlazenCompletionRequest, BlazenCompletionResponse, BlazenEmbeddingResponse,
};
use crate::provider_api_protocol::BlazenOpenAiCompatConfig;
use crate::provider_base::BlazenBaseProvider;
use crate::streaming_records::BlazenStreamChunk;
use crate::string::{alloc_cstring, cstr_to_str};
#[repr(C)]
pub struct BlazenCustomProvider(pub(crate) Arc<InnerCustomProviderHandle>);
impl BlazenCustomProvider {
pub(crate) fn into_ptr(self) -> *mut BlazenCustomProvider {
Box::into_raw(Box::new(self))
}
}
impl From<Arc<InnerCustomProviderHandle>> for BlazenCustomProvider {
fn from(inner: Arc<InnerCustomProviderHandle>) -> Self {
Self(inner)
}
}
impl From<InnerCustomProviderHandle> for BlazenCustomProvider {
fn from(inner: InnerCustomProviderHandle) -> Self {
Self(Arc::new(inner))
}
}
fn uniffi_err_to_llm(err: UniffiError) -> LlmError {
match err {
UniffiError::Auth { message } => LlmError::Auth { message },
UniffiError::RateLimit { retry_after_ms, .. } => LlmError::RateLimit { retry_after_ms },
UniffiError::Timeout { elapsed_ms, .. } => LlmError::Timeout { elapsed_ms },
UniffiError::Validation { message } => LlmError::Validation {
field: None,
message,
},
UniffiError::ContentPolicy { message } => LlmError::ContentPolicy { message },
UniffiError::Unsupported { message } => LlmError::Unsupported { message },
UniffiError::Tool { message } => LlmError::Tool {
name: None,
message,
},
UniffiError::Provider {
provider,
message,
status,
..
} => LlmError::Provider {
provider: provider.unwrap_or_default(),
message,
status_code: status.and_then(|s| u16::try_from(s).ok()),
},
UniffiError::Compute { message }
| UniffiError::Media { message }
| UniffiError::Internal { message }
| UniffiError::Workflow { message }
| UniffiError::Peer { message, .. }
| UniffiError::Persist { message }
| UniffiError::Prompt { message, .. }
| UniffiError::Memory { message, .. }
| UniffiError::Cache { message, .. } => LlmError::request(message),
UniffiError::Cancelled => LlmError::request("cancelled"),
}
}
#[repr(C)]
pub struct BlazenCustomProviderVTable {
pub user_data: *mut c_void,
pub drop_user_data: extern "C" fn(user_data: *mut c_void),
pub provider_id: *const c_char,
pub model_id: *const c_char,
pub complete: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenCompletionRequest,
out_response: *mut *mut BlazenCompletionResponse,
out_err: *mut *mut BlazenError,
) -> i32,
pub stream: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenCompletionRequest,
pusher: *mut BlazenStreamPusher,
out_err: *mut *mut BlazenError,
) -> i32,
pub embed: extern "C" fn(
user_data: *mut c_void,
texts: *const *const c_char,
count: usize,
out_response: *mut *mut BlazenEmbeddingResponse,
out_err: *mut *mut BlazenError,
) -> i32,
pub text_to_speech: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenSpeechRequest,
out_result: *mut *mut BlazenAudioResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub generate_music: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenMusicRequest,
out_result: *mut *mut BlazenAudioResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub generate_sfx: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenMusicRequest,
out_result: *mut *mut BlazenAudioResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub clone_voice: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenVoiceCloneRequest,
out_result: *mut *mut BlazenVoiceHandle,
out_err: *mut *mut BlazenError,
) -> i32,
pub list_voices: extern "C" fn(
user_data: *mut c_void,
out_array: *mut *mut *mut BlazenVoiceHandle,
out_count: *mut usize,
out_err: *mut *mut BlazenError,
) -> i32,
pub delete_voice: extern "C" fn(
user_data: *mut c_void,
voice: *mut BlazenVoiceHandle,
out_err: *mut *mut BlazenError,
) -> i32,
pub generate_image: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenImageRequest,
out_result: *mut *mut BlazenImageResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub upscale_image: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenUpscaleRequest,
out_result: *mut *mut BlazenImageResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub text_to_video: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenVideoRequest,
out_result: *mut *mut BlazenVideoResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub image_to_video: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenVideoRequest,
out_result: *mut *mut BlazenVideoResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub transcribe: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenTranscriptionRequest,
out_result: *mut *mut BlazenTranscriptionResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub generate_3d: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenThreeDRequest,
out_result: *mut *mut BlazenThreeDResult,
out_err: *mut *mut BlazenError,
) -> i32,
pub remove_background: extern "C" fn(
user_data: *mut c_void,
request: *mut BlazenBackgroundRemovalRequest,
out_result: *mut *mut BlazenImageResult,
out_err: *mut *mut BlazenError,
) -> i32,
}
unsafe impl Send for BlazenCustomProviderVTable {}
unsafe impl Sync for BlazenCustomProviderVTable {}
pub struct BlazenStreamPusher {
tx: tokio::sync::mpsc::UnboundedSender<Result<LlmStreamChunk, UniffiError>>,
}
impl BlazenStreamPusher {
fn into_ptr(self) -> *mut BlazenStreamPusher {
Box::into_raw(Box::new(self))
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_stream_pusher_push(
pusher: *mut BlazenStreamPusher,
chunk: *mut BlazenStreamChunk,
) -> i32 {
if pusher.is_null() || chunk.is_null() {
if !chunk.is_null() {
drop(unsafe { Box::from_raw(chunk) });
}
return -1;
}
let p = unsafe { &*pusher };
let chunk_box = unsafe { Box::from_raw(chunk) };
let uniffi_chunk: UniffiStreamChunk = chunk_box.0;
let delta = if uniffi_chunk.content_delta.is_empty() {
None
} else {
Some(uniffi_chunk.content_delta)
};
let finish_reason = if uniffi_chunk.is_final {
Some("stop".to_owned())
} else {
None
};
let llm_chunk = LlmStreamChunk {
delta,
finish_reason,
..LlmStreamChunk::default()
};
match p.tx.send(Ok(llm_chunk)) {
Ok(()) => 0,
Err(_) => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_stream_pusher_end(pusher: *mut BlazenStreamPusher) {
if pusher.is_null() {
return;
}
drop(unsafe { Box::from_raw(pusher) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_stream_pusher_error(
pusher: *mut BlazenStreamPusher,
err: *mut BlazenError,
) {
if pusher.is_null() {
if !err.is_null() {
drop(unsafe { Box::from_raw(err) });
}
return;
}
let p_box = unsafe { Box::from_raw(pusher) };
let inner_err = if err.is_null() {
UniffiError::Internal {
message: "stream pusher closed without an error payload".into(),
}
} else {
unsafe { Box::from_raw(err) }.inner
};
let _ = p_box.tx.send(Err(inner_err));
}
pub struct BlazenCabiCustomProviderAdapter {
vtable: BlazenCustomProviderVTable,
provider_id_cached: String,
model_id_cached: String,
}
impl Drop for BlazenCabiCustomProviderAdapter {
fn drop(&mut self) {
(self.vtable.drop_user_data)(self.vtable.user_data);
}
}
impl std::fmt::Debug for BlazenCabiCustomProviderAdapter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlazenCabiCustomProviderAdapter")
.field("provider_id", &self.provider_id_cached)
.field("model_id", &self.model_id_cached)
.finish_non_exhaustive()
}
}
impl BlazenCabiCustomProviderAdapter {
fn new(vtable: BlazenCustomProviderVTable) -> Self {
let provider_id_cached = unsafe { cstr_to_str(vtable.provider_id) }
.unwrap_or("custom")
.to_owned();
let model_id_cached = unsafe { cstr_to_str(vtable.model_id) }
.map_or_else(|| provider_id_cached.clone(), str::to_owned);
Self {
vtable,
provider_id_cached,
model_id_cached,
}
}
}
async fn dispatch_typed_with_request<Req, ResHandle, Res>(
user_data_addr: usize,
func: extern "C" fn(*mut c_void, *mut Req, *mut *mut ResHandle, *mut *mut BlazenError) -> i32,
req_addr: usize,
) -> Result<Res, LlmError>
where
Req: 'static,
ResHandle: 'static,
Res: Send + 'static,
Box<ResHandle>: TakeInner<Res>,
{
let join =
tokio::task::spawn_blocking(move || -> Result<Res, UniffiError> {
let user_data = user_data_addr as *mut c_void;
let req_ptr = req_addr as *mut Req;
let mut out_handle: *mut ResHandle = std::ptr::null_mut();
let mut out_err: *mut BlazenError = std::ptr::null_mut();
let status = func(user_data, req_ptr, &raw mut out_handle, &raw mut out_err);
if status == 0 {
if out_handle.is_null() {
return Err(UniffiError::Internal {
message: "CustomProvider vtable returned 0 without writing out_handle"
.into(),
});
}
let boxed = unsafe { Box::from_raw(out_handle) };
Ok(<Box<ResHandle> as TakeInner<Res>>::take_inner(boxed))
} else {
if out_err.is_null() {
return Err(UniffiError::Internal {
message: format!(
"CustomProvider vtable returned -1 (status={status}) without writing out_err"
),
});
}
let be = unsafe { Box::from_raw(out_err) };
Err(be.inner)
}
})
.await;
match join {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(uniffi_err_to_llm(e)),
Err(join_err) => Err(LlmError::request(format!(
"CustomProvider vtable task panicked: {join_err}"
))),
}
}
trait TakeInner<Out> {
fn take_inner(self) -> Out;
}
impl TakeInner<InnerAudioResult> for Box<BlazenAudioResult> {
fn take_inner(self) -> InnerAudioResult {
self.0
}
}
impl TakeInner<InnerImageResult> for Box<BlazenImageResult> {
fn take_inner(self) -> InnerImageResult {
self.0
}
}
impl TakeInner<InnerVideoResult> for Box<BlazenVideoResult> {
fn take_inner(self) -> InnerVideoResult {
self.0
}
}
impl TakeInner<InnerTranscriptionResult> for Box<BlazenTranscriptionResult> {
fn take_inner(self) -> InnerTranscriptionResult {
self.0
}
}
impl TakeInner<InnerThreeDResult> for Box<BlazenThreeDResult> {
fn take_inner(self) -> InnerThreeDResult {
self.0
}
}
impl TakeInner<InnerVoiceHandle> for Box<BlazenVoiceHandle> {
fn take_inner(self) -> InnerVoiceHandle {
self.0
}
}
#[async_trait]
impl InnerCustomProviderTrait for BlazenCabiCustomProviderAdapter {
fn provider_id(&self) -> &str {
&self.provider_id_cached
}
fn model_id(&self) -> &str {
&self.model_id_cached
}
async fn complete(
&self,
request: LlmCompletionRequest,
) -> Result<LlmCompletionResponse, LlmError> {
let uniffi_req: UniffiCompletionRequest = llm_to_uniffi_request(request);
let req_handle = BlazenCompletionRequest::from(uniffi_req).into_ptr();
let user_data_addr = self.vtable.user_data as usize;
let complete_fn = self.vtable.complete;
let req_addr = req_handle as usize;
let join = tokio::task::spawn_blocking(move || -> Result<LlmCompletionResponse, UniffiError> {
let user_data = user_data_addr as *mut c_void;
let req_ptr = req_addr as *mut BlazenCompletionRequest;
let mut out_response: *mut BlazenCompletionResponse = std::ptr::null_mut();
let mut out_err: *mut BlazenError = std::ptr::null_mut();
let status = complete_fn(user_data, req_ptr, &raw mut out_response, &raw mut out_err);
if status == 0 {
if out_response.is_null() {
return Err(UniffiError::Internal {
message: "CustomProvider complete returned 0 without writing out_response".into(),
});
}
let resp_box = unsafe { Box::from_raw(out_response) };
Ok(uniffi_to_llm_response(resp_box.0))
} else {
if out_err.is_null() {
return Err(UniffiError::Internal {
message: format!("CustomProvider complete returned -1 (status={status}) without writing out_err"),
});
}
let be = unsafe { Box::from_raw(out_err) };
Err(be.inner)
}
})
.await;
match join {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(uniffi_err_to_llm(e)),
Err(join_err) => Err(LlmError::request(format!(
"CustomProvider complete task panicked: {join_err}"
))),
}
}
async fn stream(
&self,
request: LlmCompletionRequest,
) -> Result<Pin<Box<dyn Stream<Item = Result<LlmStreamChunk, LlmError>> + Send>>, LlmError>
{
let uniffi_req: UniffiCompletionRequest = llm_to_uniffi_request(request);
let req_handle = BlazenCompletionRequest::from(uniffi_req).into_ptr();
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let pusher_ptr = BlazenStreamPusher { tx }.into_ptr();
let user_data_addr = self.vtable.user_data as usize;
let stream_fn = self.vtable.stream;
let req_addr = req_handle as usize;
let pusher_addr = pusher_ptr as usize;
let start_status = tokio::task::spawn_blocking(move || -> Result<(), UniffiError> {
let user_data = user_data_addr as *mut c_void;
let req_ptr = req_addr as *mut BlazenCompletionRequest;
let pusher = pusher_addr as *mut BlazenStreamPusher;
let mut out_err: *mut BlazenError = std::ptr::null_mut();
let status = stream_fn(user_data, req_ptr, pusher, &raw mut out_err);
if status == 0 {
Ok(())
} else {
if out_err.is_null() {
return Err(UniffiError::Internal {
message: format!(
"CustomProvider stream returned -1 (status={status}) without writing out_err"
),
});
}
let be = unsafe { Box::from_raw(out_err) };
Err(be.inner)
}
})
.await;
match start_status {
Ok(Ok(())) => {}
Ok(Err(e)) => return Err(uniffi_err_to_llm(e)),
Err(join_err) => {
return Err(LlmError::request(format!(
"CustomProvider stream start task panicked: {join_err}"
)));
}
}
let s = stream::unfold(rx, |mut rx| async move {
match rx.recv().await {
Some(Ok(chunk)) => Some((Ok(chunk), rx)),
Some(Err(e)) => Some((Err(uniffi_err_to_llm(e)), rx)),
None => None,
}
});
Ok(Box::pin(s))
}
async fn embed(&self, texts: Vec<String>) -> Result<LlmEmbeddingResponse, LlmError> {
let owned: Vec<std::ffi::CString> = texts
.into_iter()
.map(|t| {
std::ffi::CString::new(t).unwrap_or_else(|_| std::ffi::CString::new("").unwrap())
})
.collect();
let user_data_addr = self.vtable.user_data as usize;
let embed_fn = self.vtable.embed;
let join = tokio::task::spawn_blocking(
move || -> Result<LlmEmbeddingResponse, UniffiError> {
let user_data = user_data_addr as *mut c_void;
let ptrs: Vec<*const c_char> = owned.iter().map(|s| s.as_ptr()).collect();
let count = ptrs.len();
let mut out_response: *mut BlazenEmbeddingResponse = std::ptr::null_mut();
let mut out_err: *mut BlazenError = std::ptr::null_mut();
let status = embed_fn(
user_data,
ptrs.as_ptr(),
count,
&raw mut out_response,
&raw mut out_err,
);
let _keepalive = &owned;
if status == 0 {
if out_response.is_null() {
return Err(UniffiError::Internal {
message: "CustomProvider embed returned 0 without writing out_response"
.into(),
});
}
let resp_box = unsafe { Box::from_raw(out_response) };
Ok(uniffi_to_llm_embedding_response(resp_box.0))
} else {
if out_err.is_null() {
return Err(UniffiError::Internal {
message: format!(
"CustomProvider embed returned -1 (status={status}) without writing out_err"
),
});
}
let be = unsafe { Box::from_raw(out_err) };
Err(be.inner)
}
},
)
.await;
match join {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(uniffi_err_to_llm(e)),
Err(join_err) => Err(LlmError::request(format!(
"CustomProvider embed task panicked: {join_err}"
))),
}
}
async fn text_to_speech(&self, req: InnerSpeechRequest) -> Result<InnerAudioResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.text_to_speech;
let req_ptr = BlazenSpeechRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenSpeechRequest, BlazenAudioResult, InnerAudioResult>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn generate_music(&self, req: InnerMusicRequest) -> Result<InnerAudioResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.generate_music;
let req_ptr = BlazenMusicRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenMusicRequest, BlazenAudioResult, InnerAudioResult>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn generate_sfx(&self, req: InnerMusicRequest) -> Result<InnerAudioResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.generate_sfx;
let req_ptr = BlazenMusicRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenMusicRequest, BlazenAudioResult, InnerAudioResult>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn clone_voice(&self, req: InnerVoiceCloneRequest) -> Result<InnerVoiceHandle, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.clone_voice;
let req_ptr = BlazenVoiceCloneRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenVoiceCloneRequest, BlazenVoiceHandle, InnerVoiceHandle>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn list_voices(&self) -> Result<Vec<InnerVoiceHandle>, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.list_voices;
let join = tokio::task::spawn_blocking(move || -> Result<Vec<InnerVoiceHandle>, UniffiError> {
let user_data = user_data_addr as *mut c_void;
let mut out_array: *mut *mut BlazenVoiceHandle = std::ptr::null_mut();
let mut out_count: usize = 0;
let mut out_err: *mut BlazenError = std::ptr::null_mut();
let status = f(user_data, &raw mut out_array, &raw mut out_count, &raw mut out_err);
if status == 0 {
if out_count == 0 {
return Ok(Vec::new());
}
if out_array.is_null() {
return Err(UniffiError::Internal {
message: "CustomProvider list_voices returned non-zero count with null array".into(),
});
}
let mut out: Vec<InnerVoiceHandle> = Vec::with_capacity(out_count);
for i in 0..out_count {
let h_ptr = unsafe { *out_array.add(i) };
if h_ptr.is_null() {
return Err(UniffiError::Internal {
message: "CustomProvider list_voices: null entry in voice array".into(),
});
}
let h_box = unsafe { Box::from_raw(h_ptr) };
out.push(h_box.0);
}
let slice_ptr: *mut [*mut BlazenVoiceHandle] =
std::ptr::slice_from_raw_parts_mut(out_array, out_count);
drop(unsafe { Box::from_raw(slice_ptr) });
Ok(out)
} else {
if out_err.is_null() {
return Err(UniffiError::Internal {
message: format!(
"CustomProvider list_voices returned -1 (status={status}) without writing out_err"
),
});
}
let be = unsafe { Box::from_raw(out_err) };
Err(be.inner)
}
})
.await;
match join {
Ok(Ok(v)) => Ok(v),
Ok(Err(e)) => Err(uniffi_err_to_llm(e)),
Err(join_err) => Err(LlmError::request(format!(
"CustomProvider list_voices task panicked: {join_err}"
))),
}
}
async fn delete_voice(&self, voice: InnerVoiceHandle) -> Result<(), LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.delete_voice;
let v_ptr = BlazenVoiceHandle::from(voice).into_ptr();
let v_addr = v_ptr as usize;
let join = tokio::task::spawn_blocking(move || -> Result<(), UniffiError> {
let user_data = user_data_addr as *mut c_void;
let v_ptr = v_addr as *mut BlazenVoiceHandle;
let mut out_err: *mut BlazenError = std::ptr::null_mut();
let status = f(user_data, v_ptr, &raw mut out_err);
if status == 0 {
Ok(())
} else {
if out_err.is_null() {
return Err(UniffiError::Internal {
message: format!(
"CustomProvider delete_voice returned -1 (status={status}) without writing out_err"
),
});
}
let be = unsafe { Box::from_raw(out_err) };
Err(be.inner)
}
})
.await;
match join {
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => Err(uniffi_err_to_llm(e)),
Err(join_err) => Err(LlmError::request(format!(
"CustomProvider delete_voice task panicked: {join_err}"
))),
}
}
async fn generate_image(&self, req: InnerImageRequest) -> Result<InnerImageResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.generate_image;
let req_ptr = BlazenImageRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenImageRequest, BlazenImageResult, InnerImageResult>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn upscale_image(&self, req: InnerUpscaleRequest) -> Result<InnerImageResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.upscale_image;
let req_ptr = BlazenUpscaleRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenUpscaleRequest, BlazenImageResult, InnerImageResult>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn text_to_video(&self, req: InnerVideoRequest) -> Result<InnerVideoResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.text_to_video;
let req_ptr = BlazenVideoRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenVideoRequest, BlazenVideoResult, InnerVideoResult>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn image_to_video(&self, req: InnerVideoRequest) -> Result<InnerVideoResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.image_to_video;
let req_ptr = BlazenVideoRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenVideoRequest, BlazenVideoResult, InnerVideoResult>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn transcribe(
&self,
req: InnerTranscriptionRequest,
) -> Result<InnerTranscriptionResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.transcribe;
let req_ptr = BlazenTranscriptionRequest::from(req).into_ptr();
dispatch_typed_with_request::<
BlazenTranscriptionRequest,
BlazenTranscriptionResult,
InnerTranscriptionResult,
>(user_data_addr, f, req_ptr as usize)
.await
}
async fn generate_3d(&self, req: InnerThreeDRequest) -> Result<InnerThreeDResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.generate_3d;
let req_ptr = BlazenThreeDRequest::from(req).into_ptr();
dispatch_typed_with_request::<BlazenThreeDRequest, BlazenThreeDResult, InnerThreeDResult>(
user_data_addr,
f,
req_ptr as usize,
)
.await
}
async fn remove_background(
&self,
req: InnerBackgroundRemovalRequest,
) -> Result<InnerImageResult, LlmError> {
let user_data_addr = self.vtable.user_data as usize;
let f = self.vtable.remove_background;
let req_ptr = BlazenBackgroundRemovalRequest::from(req).into_ptr();
dispatch_typed_with_request::<
BlazenBackgroundRemovalRequest,
BlazenImageResult,
InnerImageResult,
>(user_data_addr, f, req_ptr as usize)
.await
}
}
fn llm_to_uniffi_request(req: LlmCompletionRequest) -> UniffiCompletionRequest {
let response_format_json = req.response_format.map(|v| v.to_string());
let messages = req
.messages
.into_iter()
.map(llm_message_to_uniffi)
.collect();
let tools = req
.tools
.into_iter()
.map(|t| blazen_uniffi::llm::Tool {
name: t.name,
description: t.description,
parameters_json: t.parameters.to_string(),
})
.collect();
UniffiCompletionRequest {
messages,
tools,
temperature: req.temperature.map(f64::from),
max_tokens: req.max_tokens,
top_p: req.top_p.map(f64::from),
model: req.model,
response_format_json,
system: None,
}
}
fn llm_message_to_uniffi(msg: blazen_llm::types::ChatMessage) -> blazen_uniffi::llm::ChatMessage {
use blazen_llm::types::{ContentPart, ImageSource, MessageContent, Role};
let role = match msg.role {
Role::System => "system",
Role::User => "user",
Role::Assistant => "assistant",
Role::Tool => "tool",
}
.to_owned();
let (content, media_parts) = match msg.content {
MessageContent::Text(s) => (s, Vec::new()),
MessageContent::Image(img) => (String::new(), vec![image_content_to_media(img)]),
MessageContent::Parts(parts) => {
let mut text = String::new();
let mut media = Vec::new();
for part in parts {
match part {
ContentPart::Text { text: t } => {
if !text.is_empty() {
text.push('\n');
}
text.push_str(&t);
}
ContentPart::Image(img) => {
media.push(image_content_to_media(img));
}
ContentPart::Audio(a) => {
let m = blazen_uniffi::llm::Media {
kind: "audio".into(),
mime_type: a.media_type.unwrap_or_default(),
data_base64: match a.source {
ImageSource::Base64 { data } => data,
other => serde_json::to_string(&other).unwrap_or_default(),
},
};
media.push(m);
}
ContentPart::Video(v) => {
let m = blazen_uniffi::llm::Media {
kind: "video".into(),
mime_type: v.media_type.unwrap_or_default(),
data_base64: match v.source {
ImageSource::Base64 { data } => data,
other => serde_json::to_string(&other).unwrap_or_default(),
},
};
media.push(m);
}
ContentPart::File(_) => {}
}
}
(text, media)
}
};
let tool_calls = msg
.tool_calls
.into_iter()
.map(|tc| blazen_uniffi::llm::ToolCall {
id: tc.id,
name: tc.name,
arguments_json: tc.arguments.to_string(),
})
.collect();
blazen_uniffi::llm::ChatMessage {
role,
content,
media_parts,
tool_calls,
tool_call_id: msg.tool_call_id,
name: msg.name,
}
}
fn image_content_to_media(img: blazen_llm::types::ImageContent) -> blazen_uniffi::llm::Media {
use blazen_llm::types::ImageSource;
let data_base64 = match img.source {
ImageSource::Base64 { data } => data,
other => serde_json::to_string(&other).unwrap_or_default(),
};
blazen_uniffi::llm::Media {
kind: "image".into(),
mime_type: img.media_type.unwrap_or_default(),
data_base64,
}
}
fn uniffi_to_llm_response(resp: UniffiCompletionResponse) -> LlmCompletionResponse {
let finish_reason = if resp.finish_reason.is_empty() {
None
} else {
Some(resp.finish_reason)
};
let usage = if resp.usage.total_tokens == 0
&& resp.usage.prompt_tokens == 0
&& resp.usage.completion_tokens == 0
{
None
} else {
Some(blazen_llm::types::TokenUsage {
prompt_tokens: u32::try_from(resp.usage.prompt_tokens).unwrap_or(u32::MAX),
completion_tokens: u32::try_from(resp.usage.completion_tokens).unwrap_or(u32::MAX),
total_tokens: u32::try_from(resp.usage.total_tokens).unwrap_or(u32::MAX),
cached_input_tokens: u32::try_from(resp.usage.cached_input_tokens).unwrap_or(u32::MAX),
reasoning_tokens: u32::try_from(resp.usage.reasoning_tokens).unwrap_or(u32::MAX),
audio_input_tokens: 0,
audio_output_tokens: 0,
})
};
let tool_calls = resp
.tool_calls
.into_iter()
.map(|tc| blazen_llm::types::ToolCall {
id: tc.id,
name: tc.name,
arguments: serde_json::from_str(&tc.arguments_json).unwrap_or_default(),
})
.collect();
LlmCompletionResponse {
content: if resp.content.is_empty() {
None
} else {
Some(resp.content)
},
tool_calls,
reasoning: None,
citations: Vec::new(),
artifacts: Vec::new(),
usage,
model: resp.model,
finish_reason,
cost: None,
timing: None,
images: Vec::new(),
audio: Vec::new(),
videos: Vec::new(),
metadata: serde_json::Value::Null,
}
}
fn uniffi_to_llm_embedding_response(resp: UniffiEmbeddingResponse) -> LlmEmbeddingResponse {
#[allow(clippy::cast_possible_truncation)]
let embeddings: Vec<Vec<f32>> = resp
.embeddings
.into_iter()
.map(|v| v.into_iter().map(|d| d as f32).collect())
.collect();
let usage = blazen_llm::types::TokenUsage {
prompt_tokens: u32::try_from(resp.usage.prompt_tokens).unwrap_or(u32::MAX),
completion_tokens: u32::try_from(resp.usage.completion_tokens).unwrap_or(u32::MAX),
total_tokens: u32::try_from(resp.usage.total_tokens).unwrap_or(u32::MAX),
cached_input_tokens: u32::try_from(resp.usage.cached_input_tokens).unwrap_or(u32::MAX),
reasoning_tokens: u32::try_from(resp.usage.reasoning_tokens).unwrap_or(u32::MAX),
audio_input_tokens: 0,
audio_output_tokens: 0,
};
let usage =
if usage.total_tokens == 0 && usage.prompt_tokens == 0 && usage.completion_tokens == 0 {
None
} else {
Some(usage)
};
LlmEmbeddingResponse {
embeddings,
model: resp.model,
usage,
cost: None,
timing: None,
metadata: serde_json::Value::Null,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_from_vtable(
vtable: BlazenCustomProviderVTable,
) -> *mut BlazenCustomProvider {
let adapter =
Arc::new(BlazenCabiCustomProviderAdapter::new(vtable)) as Arc<dyn InnerCustomProviderTrait>;
let handle = InnerCustomProviderHandle::new(adapter);
BlazenCustomProvider::from(handle).into_ptr()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_openai_compat(
provider_id: *const c_char,
config: *const BlazenOpenAiCompatConfig,
) -> *mut BlazenCustomProvider {
if config.is_null() {
return std::ptr::null_mut();
}
let Some(provider_id) = (unsafe { cstr_to_str(provider_id) }) else {
return std::ptr::null_mut();
};
let cfg = unsafe { &*config }.0.clone();
let handle = inner_custom::openai_compat(provider_id.to_owned(), cfg);
BlazenCustomProvider::from(handle).into_ptr()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_ollama(
model: *const c_char,
host: *const c_char,
port: u16,
) -> *mut BlazenCustomProvider {
let Some(model) = (unsafe { cstr_to_str(model) }) else {
return std::ptr::null_mut();
};
let host_str = unsafe { cstr_to_str(host) }.unwrap_or("localhost");
let resolved_port = if port == 0 { 11434 } else { port };
let handle = inner_custom::ollama(host_str, resolved_port, model.to_owned());
BlazenCustomProvider::from(handle).into_ptr()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_lm_studio(
model: *const c_char,
host: *const c_char,
port: u16,
) -> *mut BlazenCustomProvider {
let Some(model) = (unsafe { cstr_to_str(model) }) else {
return std::ptr::null_mut();
};
let host_str = unsafe { cstr_to_str(host) }.unwrap_or("localhost");
let resolved_port = if port == 0 { 1234 } else { port };
let handle = inner_custom::lm_studio(host_str, resolved_port, model.to_owned());
BlazenCustomProvider::from(handle).into_ptr()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_provider_id(
p: *const BlazenCustomProvider,
) -> *mut c_char {
if p.is_null() {
return std::ptr::null_mut();
}
let h = unsafe { &*p };
alloc_cstring(h.0.provider_id_str())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_model_id(
p: *const BlazenCustomProvider,
) -> *mut c_char {
if p.is_null() {
return std::ptr::null_mut();
}
let h = unsafe { &*p };
alloc_cstring(<InnerCustomProviderHandle as CompletionModel>::model_id(
&h.0,
))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_as_base_provider(
p: *const BlazenCustomProvider,
) -> *mut BlazenBaseProvider {
if p.is_null() {
return std::ptr::null_mut();
}
let inner: Arc<dyn CompletionModel> = unsafe { (*p).0.clone() };
let bp = InnerBaseProvider::new(inner);
BlazenBaseProvider::from(bp).into_ptr()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_free(p: *mut BlazenCustomProvider) {
if p.is_null() {
return;
}
drop(unsafe { Box::from_raw(p) });
}
#[inline]
unsafe fn clone_provider(p: *const BlazenCustomProvider) -> Arc<InnerCustomProviderHandle> {
Arc::clone(unsafe { &(*p).0 })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_text_to_speech(
p: *const BlazenCustomProvider,
request: *mut BlazenSpeechRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerAudioResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::text_to_speech(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_generate_music(
p: *const BlazenCustomProvider,
request: *mut BlazenMusicRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerAudioResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::generate_music(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_generate_sfx(
p: *const BlazenCustomProvider,
request: *mut BlazenMusicRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerAudioResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::generate_sfx(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_clone_voice(
p: *const BlazenCustomProvider,
request: *mut BlazenVoiceCloneRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerVoiceHandle, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::clone_voice(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_list_voices(
p: *const BlazenCustomProvider,
) -> *mut BlazenFuture {
if p.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
BlazenFuture::spawn::<Vec<InnerVoiceHandle>, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::list_voices(&provider)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_delete_voice(
p: *const BlazenCustomProvider,
voice: *mut BlazenVoiceHandle,
) -> *mut BlazenFuture {
if p.is_null() || voice.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let v = unsafe { Box::from_raw(voice) }.0;
BlazenFuture::spawn::<(), _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::delete_voice(&provider, v)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_generate_image(
p: *const BlazenCustomProvider,
request: *mut BlazenImageRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerImageResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::generate_image(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_upscale_image(
p: *const BlazenCustomProvider,
request: *mut BlazenUpscaleRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerImageResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::upscale_image(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_text_to_video(
p: *const BlazenCustomProvider,
request: *mut BlazenVideoRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerVideoResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::text_to_video(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_image_to_video(
p: *const BlazenCustomProvider,
request: *mut BlazenVideoRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerVideoResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::image_to_video(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_transcribe(
p: *const BlazenCustomProvider,
request: *mut BlazenTranscriptionRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerTranscriptionResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::transcribe(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_generate_3d(
p: *const BlazenCustomProvider,
request: *mut BlazenThreeDRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerThreeDResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::generate_3d(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_custom_provider_remove_background(
p: *const BlazenCustomProvider,
request: *mut BlazenBackgroundRemovalRequest,
) -> *mut BlazenFuture {
if p.is_null() || request.is_null() {
return std::ptr::null_mut();
}
let provider = unsafe { clone_provider(p) };
let req = unsafe { Box::from_raw(request) }.0;
BlazenFuture::spawn::<InnerImageResult, _>(async move {
<InnerCustomProviderHandle as InnerCustomProviderTrait>::remove_background(&provider, req)
.await
.map_err(Into::into)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_future_take_custom_audio_result(
fut: *mut BlazenFuture,
out: *mut *mut BlazenAudioResult,
err: *mut *mut BlazenError,
) -> i32 {
match unsafe { BlazenFuture::take_typed::<InnerAudioResult>(fut) } {
Ok(v) => {
if !out.is_null() {
unsafe {
*out = BlazenAudioResult::from(v).into_ptr();
}
}
0
}
Err(e) => {
if !err.is_null() {
unsafe {
*err = BlazenError::from(e).into_ptr();
}
}
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_future_take_custom_image_result(
fut: *mut BlazenFuture,
out: *mut *mut BlazenImageResult,
err: *mut *mut BlazenError,
) -> i32 {
match unsafe { BlazenFuture::take_typed::<InnerImageResult>(fut) } {
Ok(v) => {
if !out.is_null() {
unsafe {
*out = BlazenImageResult::from(v).into_ptr();
}
}
0
}
Err(e) => {
if !err.is_null() {
unsafe {
*err = BlazenError::from(e).into_ptr();
}
}
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_future_take_custom_video_result(
fut: *mut BlazenFuture,
out: *mut *mut BlazenVideoResult,
err: *mut *mut BlazenError,
) -> i32 {
match unsafe { BlazenFuture::take_typed::<InnerVideoResult>(fut) } {
Ok(v) => {
if !out.is_null() {
unsafe {
*out = BlazenVideoResult::from(v).into_ptr();
}
}
0
}
Err(e) => {
if !err.is_null() {
unsafe {
*err = BlazenError::from(e).into_ptr();
}
}
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_future_take_custom_transcription_result(
fut: *mut BlazenFuture,
out: *mut *mut BlazenTranscriptionResult,
err: *mut *mut BlazenError,
) -> i32 {
match unsafe { BlazenFuture::take_typed::<InnerTranscriptionResult>(fut) } {
Ok(v) => {
if !out.is_null() {
unsafe {
*out = BlazenTranscriptionResult::from(v).into_ptr();
}
}
0
}
Err(e) => {
if !err.is_null() {
unsafe {
*err = BlazenError::from(e).into_ptr();
}
}
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_future_take_custom_three_d_result(
fut: *mut BlazenFuture,
out: *mut *mut BlazenThreeDResult,
err: *mut *mut BlazenError,
) -> i32 {
match unsafe { BlazenFuture::take_typed::<InnerThreeDResult>(fut) } {
Ok(v) => {
if !out.is_null() {
unsafe {
*out = BlazenThreeDResult::from(v).into_ptr();
}
}
0
}
Err(e) => {
if !err.is_null() {
unsafe {
*err = BlazenError::from(e).into_ptr();
}
}
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_future_take_custom_voice_handle(
fut: *mut BlazenFuture,
out: *mut *mut BlazenVoiceHandle,
err: *mut *mut BlazenError,
) -> i32 {
match unsafe { BlazenFuture::take_typed::<InnerVoiceHandle>(fut) } {
Ok(v) => {
if !out.is_null() {
unsafe {
*out = BlazenVoiceHandle::from(v).into_ptr();
}
}
0
}
Err(e) => {
if !err.is_null() {
unsafe {
*err = BlazenError::from(e).into_ptr();
}
}
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_future_take_custom_voice_list(
fut: *mut BlazenFuture,
out_array: *mut *mut *mut BlazenVoiceHandle,
out_count: *mut usize,
err: *mut *mut BlazenError,
) -> i32 {
match unsafe { BlazenFuture::take_typed::<Vec<InnerVoiceHandle>>(fut) } {
Ok(v) => {
let count = v.len();
let boxed_slice: Box<[*mut BlazenVoiceHandle]> = v
.into_iter()
.map(|h| BlazenVoiceHandle::from(h).into_ptr())
.collect();
let raw_slice = Box::into_raw(boxed_slice);
let ptr: *mut *mut BlazenVoiceHandle = raw_slice.cast::<*mut BlazenVoiceHandle>();
if out_array.is_null() {
let _ = ptr;
} else {
unsafe {
*out_array = ptr;
}
}
if !out_count.is_null() {
unsafe {
*out_count = count;
}
}
0
}
Err(e) => {
if !err.is_null() {
unsafe {
*err = BlazenError::from(e).into_ptr();
}
}
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_voice_handle_list_free(
array: *mut *mut BlazenVoiceHandle,
count: usize,
) {
if array.is_null() {
return;
}
let slice_ptr: *mut [*mut BlazenVoiceHandle] = std::ptr::slice_from_raw_parts_mut(array, count);
drop(unsafe { Box::from_raw(slice_ptr) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_future_take_custom_unit(
fut: *mut BlazenFuture,
err: *mut *mut BlazenError,
) -> i32 {
match unsafe { BlazenFuture::take_typed::<()>(fut) } {
Ok(()) => 0,
Err(e) => {
if !err.is_null() {
unsafe {
*err = BlazenError::from(e).into_ptr();
}
}
-1
}
}
}