#![allow(clippy::not_unsafe_ptr_arg_deref)]
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use serde::de::DeserializeOwned;
use aimux_core::AiMuxError;
use aimux_core::generate::{GenerateTextOptions, generate_text, stream_text};
use aimux_core::language_model::LanguageModel;
use aimux_core::message::ModelPrompt;
use aimux_core::provider::Provider;
use aimux_providers::anthropic::{AnthropicConfig, AnthropicProvider};
use aimux_providers::anthropic_aws::{AnthropicAwsProvider, AnthropicAwsProviderConfig};
use aimux_providers::azure::{AzureConfig, AzureProvider};
use aimux_providers::bedrock::{BedrockProvider, BedrockProviderConfig};
use aimux_providers::cohere::{CohereConfig, CohereProvider};
use aimux_providers::google::{GoogleConfig, GoogleProvider};
use aimux_providers::mistral::{MistralConfig, MistralProvider};
use aimux_providers::openai::{OpenAIConfig, OpenAIProvider};
use aimux_providers::tavily::{TavilyConfig, TavilyProvider};
use aimux_providers::vertex::{VertexProvider, VertexProviderConfig};
use aimux_providers::xai::{XAIConfig, XAIProvider};
use aimux_providers::{ProviderOptions, provider};
use futures::StreamExt;
use tokio::runtime::Runtime;
#[derive(Clone)]
enum ModelHandle {
Language(Arc<dyn LanguageModel>),
Embedding(Arc<dyn aimux_core::embedding_model::EmbeddingModel>),
Speech(Arc<dyn aimux_core::speech_model::SpeechModel>),
Image(Arc<dyn aimux_core::image_model::ImageModel>),
Transcription(Arc<dyn aimux_core::transcription_model::TranscriptionModel>),
Reranking(Arc<dyn aimux_core::reranking_model::RerankingModel>),
Video(Arc<dyn aimux_core::video_model::VideoModel>),
Search(Arc<dyn aimux_core::search_model::SearchModel>),
Files(Arc<dyn aimux_core::files_model::Files>),
}
type ModelRegistry = HashMap<u64, ModelHandle>;
static REGISTRY: OnceLock<Mutex<ModelRegistry>> = OnceLock::new();
static NEXT_HANDLE: AtomicU64 = AtomicU64::new(1);
fn registry() -> &'static Mutex<ModelRegistry> {
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
fn intern_model(model: Arc<dyn LanguageModel>) -> u64 {
let handle = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed);
registry()
.lock()
.expect("aimux-ffi: registry mutex poisoned")
.insert(handle, ModelHandle::Language(model));
handle
}
fn intern_handle(h: ModelHandle) -> u64 {
let handle = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed);
registry()
.lock()
.expect("aimux-ffi: registry mutex poisoned")
.insert(handle, h);
handle
}
fn get_model(handle: u64) -> Option<Arc<dyn LanguageModel>> {
match get_handle(handle)? {
ModelHandle::Language(m) => Some(m),
_ => None,
}
}
fn get_handle(handle: u64) -> Option<ModelHandle> {
registry()
.lock()
.expect("aimux-ffi: registry mutex poisoned")
.get(&handle)
.cloned()
}
fn drop_handle(handle: u64) {
registry()
.lock()
.expect("aimux-ffi: registry mutex poisoned")
.remove(&handle);
}
fn runtime() -> &'static Runtime {
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
RUNTIME.get_or_init(|| {
tokio::runtime::Runtime::new().expect("aimux-ffi: failed to build tokio runtime")
})
}
fn cstr_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_str().ok().map(str::to_owned)
}
fn parse_prompt(json: &str) -> Result<ModelPrompt, serde_json::Error> {
let value: serde_json::Value = serde_json::from_str(json)?;
let inner = match &value {
serde_json::Value::Object(obj) if obj.len() == 1 && obj.contains_key("prompt") => {
obj.get("prompt").expect("checked by guard")
}
_ => &value,
};
serde_json::from_value(inner.clone())
}
fn parse_opts(json: &str) -> Result<GenerateTextOptions, serde_json::Error> {
let trimmed = json.trim();
if trimmed.is_empty() || trimmed == "null" {
return Ok(GenerateTextOptions::default());
}
serde_json::from_str(json)
}
fn into_cstring_raw(s: String) -> *mut c_char {
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn error_json_raw(msg: impl std::fmt::Display) -> *mut c_char {
into_cstring_raw(
serde_json::json!({
"error": msg.to_string(),
"error_type": "Other",
"status_code": null,
})
.to_string(),
)
}
fn error_json_from(err: &AiMuxError) -> *mut c_char {
into_cstring_raw(
serde_json::json!({
"error": err.to_string(),
"error_type": err.error_type(),
"status_code": err.status_code(),
})
.to_string(),
)
}
fn fire_error(on_error: extern "C" fn(*const c_char), msg: impl std::fmt::Display) {
let json = serde_json::json!({
"error": msg.to_string(),
"error_type": "Other",
"status_code": null,
})
.to_string();
if let Ok(cstr) = CString::new(json) {
on_error(cstr.as_ptr());
}
}
fn fire_error_struct(on_error: extern "C" fn(*const c_char), err: &AiMuxError) {
let json = serde_json::json!({
"error": err.to_string(),
"error_type": err.error_type(),
"status_code": err.status_code(),
})
.to_string();
if let Ok(cstr) = CString::new(json) {
on_error(cstr.as_ptr());
}
}
unsafe fn parse_two_args(a: *const c_char, b: *const c_char) -> Option<(String, String)> {
match (cstr_to_string(a), cstr_to_string(b)) {
(Some(k), Some(m)) => Some((k, m)),
_ => None,
}
}
unsafe fn parse_four_args(
a: *const c_char,
b: *const c_char,
c: *const c_char,
d: *const c_char,
) -> Option<(String, String, String, String)> {
match (
cstr_to_string(a),
cstr_to_string(b),
cstr_to_string(c),
cstr_to_string(d),
) {
(Some(w), Some(x), Some(y), Some(z)) => Some((w, x, y, z)),
_ => None,
}
}
fn run_and_serialize<F, T>(_model_msg: &str, f: F) -> *mut c_char
where
F: std::future::Future<Output = Result<T, AiMuxError>>,
T: serde::Serialize,
{
let result = runtime().block_on(f);
match result {
Ok(r) => serde_json::to_string(&r)
.map(into_cstring_raw)
.unwrap_or_else(|e| error_json_raw(format!("serialize: {e}"))),
Err(e) => error_json_from(&e),
}
}
fn parse_base_url(base_url: *const c_char) -> Option<String> {
cstr_to_string(base_url).filter(|url| !url.is_empty())
}
fn parse_json_arg<T: DeserializeOwned>(json: *const c_char, name: &str) -> Result<T, *mut c_char> {
let s = match cstr_to_string(json) {
Some(s) => s,
None => return Err(error_json_raw(format!("invalid {name}"))),
};
match serde_json::from_str::<T>(&s) {
Ok(v) => Ok(v),
Err(e) => Err(error_json_raw(format!("invalid {name}: {e}"))),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
OpenAIProvider::new(OpenAIConfig::new(api_key))
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = OpenAIConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
OpenAIProvider::new(config)
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_anthropic_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
AnthropicProvider::new(AnthropicConfig::new(api_key))
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_anthropic_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = AnthropicConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
AnthropicProvider::new(config)
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_anthropic_aws_new(
api_key: *const c_char,
region: *const c_char,
model_id: *const c_char,
) -> u64 {
let parsed = match (
cstr_to_string(api_key),
cstr_to_string(region),
cstr_to_string(model_id),
) {
(Some(k), Some(r), Some(m)) => Some((k, r, m)),
_ => None,
};
let Some((api_key, region, model_id)) = parsed else {
return 0;
};
AnthropicAwsProvider::new(AnthropicAwsProviderConfig::with_api_key(api_key, region))
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_anthropic_aws_new_with_base(
api_key: *const c_char,
region: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let parsed = match (
cstr_to_string(api_key),
cstr_to_string(region),
cstr_to_string(model_id),
) {
(Some(k), Some(r), Some(m)) => Some((k, r, m)),
_ => None,
};
let Some((api_key, region, model_id)) = parsed else {
return 0;
};
let mut config = AnthropicAwsProviderConfig::with_api_key(api_key, region);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
AnthropicAwsProvider::new(config)
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_azure_new(
api_key: *const c_char,
resource_name: *const c_char,
deployment: *const c_char,
api_version: *const c_char,
) -> u64 {
let parsed = match (
cstr_to_string(api_key),
cstr_to_string(resource_name),
cstr_to_string(deployment),
) {
(Some(k), Some(r), Some(d)) => Some((k, r, d)),
_ => None,
};
let Some((api_key, resource_name, deployment)) = parsed else {
return 0;
};
let mut config = AzureConfig::new()
.with_api_key(api_key)
.with_resource_name(resource_name);
if let Some(version) = parse_base_url(api_version) {
config = config.with_api_version(version);
}
match AzureProvider::new(config) {
Ok(p) => p
.language_model(&deployment)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0),
Err(_) => 0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_azure_new_with_base(
api_key: *const c_char,
base_url: *const c_char,
deployment: *const c_char,
api_version: *const c_char,
) -> u64 {
let parsed = match (
cstr_to_string(api_key),
cstr_to_string(base_url),
cstr_to_string(deployment),
) {
(Some(k), Some(b), Some(d)) => Some((k, b, d)),
_ => None,
};
let Some((api_key, base_url, deployment)) = parsed else {
return 0;
};
let mut config = AzureConfig::new()
.with_api_key(api_key)
.with_base_url(base_url);
if let Some(version) = parse_base_url(api_version) {
config = config.with_api_version(version);
}
match AzureProvider::new(config) {
Ok(p) => p
.language_model(&deployment)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0),
Err(_) => 0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_bedrock_new(
access_key_id: *const c_char,
secret_access_key: *const c_char,
region: *const c_char,
model_id: *const c_char,
) -> u64 {
let Some((access_key_id, secret_access_key, region, model_id)) =
(unsafe { parse_four_args(access_key_id, secret_access_key, region, model_id) })
else {
return 0;
};
BedrockProvider::new(BedrockProviderConfig::new(
access_key_id,
secret_access_key,
region,
))
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_bedrock_new_with_base(
access_key_id: *const c_char,
secret_access_key: *const c_char,
region: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((access_key_id, secret_access_key, region, model_id)) =
(unsafe { parse_four_args(access_key_id, secret_access_key, region, model_id) })
else {
return 0;
};
let mut config = BedrockProviderConfig::new(access_key_id, secret_access_key, region);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
BedrockProvider::new(config)
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_vertex_new(
access_token: *const c_char,
project: *const c_char,
location: *const c_char,
model_id: *const c_char,
) -> u64 {
let Some((access_token, project, location, model_id)) =
(unsafe { parse_four_args(access_token, project, location, model_id) })
else {
return 0;
};
VertexProvider::new(VertexProviderConfig::new(access_token, project, location))
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_vertex_new_with_base(
access_token: *const c_char,
project: *const c_char,
location: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((access_token, project, location, model_id)) =
(unsafe { parse_four_args(access_token, project, location, model_id) })
else {
return 0;
};
let mut config = VertexProviderConfig::new(access_token, project, location);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
VertexProvider::new(config)
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_cohere_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
CohereProvider::new(CohereConfig::new(api_key))
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_cohere_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = CohereConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
CohereProvider::new(config)
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_mistral_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
MistralProvider::new(MistralConfig::new(api_key))
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_mistral_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = MistralConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
MistralProvider::new(config)
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_xai_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
XAIProvider::new(XAIConfig::new(api_key))
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_xai_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = XAIConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
XAIProvider::new(config)
.language_model(&model_id)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_provider_new(
name: *const c_char,
api_key: *const c_char,
model_id: *const c_char,
config_json: *const c_char,
) -> u64 {
let Some(name) = cstr_to_string(name) else {
return 0;
};
let Some(model_id) = cstr_to_string(model_id) else {
return 0;
};
let key = cstr_to_string(api_key); let opts = match cstr_to_string(config_json) {
Some(s) if !s.trim().is_empty() && s.trim() != "null" => {
match serde_json::from_str::<ProviderOptions>(&s) {
Ok(o) => Some(o),
Err(_) => return 0,
}
}
_ => None,
};
provider(&name, key, &model_id, opts)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_provider_from_env(name: *const c_char, model_id: *const c_char) -> u64 {
let Some(name) = cstr_to_string(name) else {
return 0;
};
let Some(model_id) = cstr_to_string(model_id) else {
return 0;
};
provider(&name, None, &model_id, None)
.map(|m| intern_model(Arc::from(m)))
.unwrap_or(0)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_generate_text(
handle: u64,
prompt_json: *const c_char,
opts_json: *const c_char,
) -> *mut c_char {
let model = match get_model(handle) {
Some(m) => m,
None => return error_json_raw("invalid handle"),
};
let prompt = match cstr_to_string(prompt_json).and_then(|s| parse_prompt(&s).ok()) {
Some(p) => p,
None => return error_json_raw("invalid prompt_json"),
};
let opts = match cstr_to_string(opts_json) {
Some(s) => match parse_opts(&s) {
Ok(o) => o,
Err(e) => return error_json_raw(format!("invalid opts_json: {e}")),
},
None => GenerateTextOptions::default(),
};
run_and_serialize("generate_text", async move {
generate_text(&*model, prompt, opts).await
})
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_stream_text(
handle: u64,
prompt_json: *const c_char,
opts_json: *const c_char,
on_part: extern "C" fn(*const c_char),
on_done: extern "C" fn(),
on_error: extern "C" fn(*const c_char),
) {
let model = match get_model(handle) {
Some(m) => m,
None => {
fire_error(on_error, "invalid handle");
return;
}
};
let prompt = match cstr_to_string(prompt_json).and_then(|s| parse_prompt(&s).ok()) {
Some(p) => p,
None => {
fire_error(on_error, "invalid prompt_json");
return;
}
};
let opts = match cstr_to_string(opts_json) {
Some(s) => match parse_opts(&s) {
Ok(o) => o,
Err(e) => {
fire_error(on_error, format!("invalid opts_json: {e}"));
return;
}
},
None => GenerateTextOptions::default(),
};
runtime().block_on(async move {
let stream_result = stream_text(&*model, prompt, opts).await;
match stream_result {
Ok(sr) => {
let mut stream = sr.stream;
while let Some(item) = stream.next().await {
match item {
Ok(part) => {
let json =
serde_json::to_string(&part).unwrap_or_else(|_| "{}".to_string());
if let Ok(cstr) = CString::new(json) {
on_part(cstr.as_ptr());
}
}
Err(e) => {
fire_error_struct(on_error, &e);
return;
}
}
}
on_done();
}
Err(e) => fire_error_struct(on_error, &e),
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_drop_handle(handle: u64) {
if handle != 0 {
drop_handle(handle);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aimux_free_string(ptr: *mut c_char) {
if ptr.is_null() {
return;
}
drop(unsafe { CString::from_raw(ptr) });
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_embedding_new(
api_key: *const c_char,
model_id: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = OpenAIProvider::new(OpenAIConfig::new(api_key)).embedding_model(&model_id);
intern_handle(ModelHandle::Embedding(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_embedding_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = OpenAIConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = OpenAIProvider::new(config).embedding_model(&model_id);
intern_handle(ModelHandle::Embedding(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_cohere_embedding_new(
api_key: *const c_char,
model_id: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = CohereProvider::new(CohereConfig::new(api_key)).embedding_model(&model_id);
intern_handle(ModelHandle::Embedding(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_cohere_embedding_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = CohereConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = CohereProvider::new(config).embedding_model(&model_id);
intern_handle(ModelHandle::Embedding(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_google_embedding_new(
api_key: *const c_char,
model_id: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = GoogleProvider::new(GoogleConfig::new(api_key)).embedding_model(&model_id);
intern_handle(ModelHandle::Embedding(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_google_embedding_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = GoogleConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = GoogleProvider::new(config).embedding_model(&model_id);
intern_handle(ModelHandle::Embedding(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_embed(
handle: u64,
values_json: *const c_char,
opts_json: *const c_char,
) -> *mut c_char {
let model = match get_handle(handle) {
Some(ModelHandle::Embedding(m)) => m,
_ => return error_json_raw("invalid embedding handle"),
};
let values_json = match cstr_to_string(values_json) {
Some(s) => s,
None => return error_json_raw("invalid values_json"),
};
let mut opts = aimux_core::embedding_model::EmbeddingCallOptions::new("");
if let Some(s) = cstr_to_string(opts_json)
&& !s.trim().is_empty()
&& s.trim() != "null"
{
match serde_json::from_str::<aimux_core::embedding_model::EmbeddingCallOptions>(&s) {
Ok(o) => opts = o,
Err(e) => return error_json_raw(format!("invalid opts: {e}")),
}
}
let values: Vec<String> = match serde_json::from_str(&values_json) {
Ok(v) => v,
Err(e) => return error_json_raw(format!("invalid values: {e}")),
};
opts.values = values;
run_and_serialize("embed", async move { model.do_embed(&opts).await })
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_speech_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = OpenAIProvider::new(OpenAIConfig::new(api_key)).speech(&model_id);
intern_handle(ModelHandle::Speech(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_speech_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = OpenAIConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = OpenAIProvider::new(config).speech(&model_id);
intern_handle(ModelHandle::Speech(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_speech_generate(handle: u64, opts_json: *const c_char) -> *mut c_char {
let model = match get_handle(handle) {
Some(ModelHandle::Speech(m)) => m,
_ => return error_json_raw("invalid speech handle"),
};
let opts: aimux_core::speech_model::SpeechCallOptions =
match parse_json_arg(opts_json, "opts_json") {
Ok(o) => o,
Err(e) => return e,
};
run_and_serialize("speech", async move { model.do_generate(&opts).await })
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_image_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = OpenAIProvider::new(OpenAIConfig::new(api_key)).image(&model_id);
intern_handle(ModelHandle::Image(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_image_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = OpenAIConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = OpenAIProvider::new(config).image(&model_id);
intern_handle(ModelHandle::Image(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_google_image_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = GoogleProvider::new(GoogleConfig::new(api_key)).image(&model_id);
intern_handle(ModelHandle::Image(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_google_image_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = GoogleConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = GoogleProvider::new(config).image(&model_id);
intern_handle(ModelHandle::Image(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_image_generate(handle: u64, opts_json: *const c_char) -> *mut c_char {
let model = match get_handle(handle) {
Some(ModelHandle::Image(m)) => m,
_ => return error_json_raw("invalid image handle"),
};
let opts: aimux_core::image_model::ImageCallOptions =
match parse_json_arg(opts_json, "opts_json") {
Ok(o) => o,
Err(e) => return e,
};
run_and_serialize("image", async move { model.do_generate(&opts).await })
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_transcription_new(
api_key: *const c_char,
model_id: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = OpenAIProvider::new(OpenAIConfig::new(api_key)).transcription(&model_id);
intern_handle(ModelHandle::Transcription(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_transcription_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = OpenAIConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = OpenAIProvider::new(config).transcription(&model_id);
intern_handle(ModelHandle::Transcription(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_transcription_generate(
handle: u64,
audio_base64: *const c_char,
media_type: *const c_char,
_opts_json: *const c_char,
) -> *mut c_char {
let model = match get_handle(handle) {
Some(ModelHandle::Transcription(m)) => m,
_ => return error_json_raw("invalid transcription handle"),
};
let audio_base64 = match cstr_to_string(audio_base64) {
Some(s) => s,
None => return error_json_raw("invalid audio_base64"),
};
let media_type = match cstr_to_string(media_type) {
Some(s) => s,
None => return error_json_raw("invalid media_type"),
};
let opts = aimux_core::transcription_model::TranscriptionCallOptions::new(
aimux_core::transcription_model::AudioInput::Base64(audio_base64),
media_type,
);
run_and_serialize(
"transcription",
async move { model.do_generate(&opts).await },
)
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_files_new(api_key: *const c_char) -> u64 {
let Some(api_key) = cstr_to_string(api_key) else {
return 0;
};
let files = OpenAIProvider::new(OpenAIConfig::new(api_key)).files();
intern_handle(ModelHandle::Files(Arc::new(files)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_openai_files_new_with_base(
api_key: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some(api_key) = cstr_to_string(api_key) else {
return 0;
};
let mut config = OpenAIConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let files = OpenAIProvider::new(config).files();
intern_handle(ModelHandle::Files(Arc::new(files)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_file_upload(
handle: u64,
data_base64: *const c_char,
media_type: *const c_char,
_opts_json: *const c_char,
) -> *mut c_char {
let model = match get_handle(handle) {
Some(ModelHandle::Files(m)) => m,
_ => return error_json_raw("invalid files handle"),
};
let data_base64 = match cstr_to_string(data_base64) {
Some(s) => s,
None => return error_json_raw("invalid data_base64"),
};
let media_type = match cstr_to_string(media_type) {
Some(s) => s,
None => return error_json_raw("invalid media_type"),
};
let opts = aimux_core::files_model::UploadFileCallOptions::new(
aimux_core::files_model::UploadFileData::Data {
data: aimux_core::shared::FileBytes::Base64(data_base64),
},
media_type,
);
run_and_serialize("file_upload", async move { model.upload_file(&opts).await })
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_cohere_reranking_new(
api_key: *const c_char,
model_id: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = CohereProvider::new(CohereConfig::new(api_key)).reranking_model(&model_id);
intern_handle(ModelHandle::Reranking(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_cohere_reranking_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = CohereConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = CohereProvider::new(config).reranking_model(&model_id);
intern_handle(ModelHandle::Reranking(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_rerank(handle: u64, opts_json: *const c_char) -> *mut c_char {
let model = match get_handle(handle) {
Some(ModelHandle::Reranking(m)) => m,
_ => return error_json_raw("invalid reranking handle"),
};
let opts: aimux_core::reranking_model::RerankingCallOptions =
match parse_json_arg(opts_json, "opts_json") {
Ok(o) => o,
Err(e) => return e,
};
run_and_serialize("rerank", async move { model.do_rerank(&opts).await })
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_google_video_new(api_key: *const c_char, model_id: *const c_char) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let model = GoogleProvider::new(GoogleConfig::new(api_key)).video(&model_id);
intern_handle(ModelHandle::Video(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_google_video_new_with_base(
api_key: *const c_char,
model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some((api_key, model_id)) = (unsafe { parse_two_args(api_key, model_id) }) else {
return 0;
};
let mut config = GoogleConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = GoogleProvider::new(config).video(&model_id);
intern_handle(ModelHandle::Video(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_video_generate(handle: u64, opts_json: *const c_char) -> *mut c_char {
let model = match get_handle(handle) {
Some(ModelHandle::Video(m)) => m,
_ => return error_json_raw("invalid video handle"),
};
let opts: aimux_core::video_model::VideoCallOptions =
match parse_json_arg(opts_json, "opts_json") {
Ok(o) => o,
Err(e) => return e,
};
run_and_serialize("video", async move { model.do_generate(&opts).await })
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_tavily_search_new(api_key: *const c_char, _model_id: *const c_char) -> u64 {
let Some(api_key) = cstr_to_string(api_key) else {
return 0;
};
let model = TavilyProvider::new(TavilyConfig::new(api_key)).search_model();
intern_handle(ModelHandle::Search(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_tavily_search_new_with_base(
api_key: *const c_char,
_model_id: *const c_char,
base_url: *const c_char,
) -> u64 {
let Some(api_key) = cstr_to_string(api_key) else {
return 0;
};
let mut config = TavilyConfig::new(api_key);
if let Some(url) = parse_base_url(base_url) {
config = config.with_base_url(url);
}
let model = TavilyProvider::new(config).search_model();
intern_handle(ModelHandle::Search(Arc::new(model)))
}
#[unsafe(no_mangle)]
pub extern "C" fn aimux_search(handle: u64, opts_json: *const c_char) -> *mut c_char {
let model = match get_handle(handle) {
Some(ModelHandle::Search(m)) => m,
_ => return error_json_raw("invalid search handle"),
};
let opts: aimux_core::search_model::SearchCallOptions =
match parse_json_arg(opts_json, "opts_json") {
Ok(o) => o,
Err(e) => return e,
};
run_and_serialize("search", async move { model.do_search(&opts).await })
}