foundation_models/model/
mod.rs1use core::ffi::{c_char, c_void};
5use std::ffi::CString;
6use std::path::Path;
7use std::ptr;
8use std::sync::mpsc;
9
10use serde_json::Value;
11
12use crate::error::{from_swift, FMError, Unavailability};
13use crate::ffi;
14
15fn availability_from_code(code: i32) -> Availability {
16 match code {
17 0 => Availability::Available,
18 1 => Availability::Unavailable(Unavailability::DeviceNotEligible),
19 2 => Availability::Unavailable(Unavailability::AppleIntelligenceNotEnabled),
20 3 => Availability::Unavailable(Unavailability::ModelNotReady),
21 -1 => Availability::Unavailable(Unavailability::OsTooOld),
22 _ => Availability::Unavailable(Unavailability::Unknown),
23 }
24}
25
26fn owned_string(ptr: *mut c_char) -> String {
27 if ptr.is_null() {
28 return String::new();
29 }
30 let string = unsafe { core::ffi::CStr::from_ptr(ptr) }
31 .to_string_lossy()
32 .into_owned();
33 unsafe { ffi::fm_string_free(ptr) };
34 string
35}
36
37fn json_string(ptr: *mut c_char) -> String {
38 if ptr.is_null() {
39 return String::from("[]");
40 }
41 owned_string(ptr)
42}
43
44#[derive(Debug, Clone, Copy)]
46pub struct SystemLanguageModel;
47
48impl SystemLanguageModel {
49 #[must_use]
51 pub fn is_available() -> bool {
52 unsafe { ffi::fm_system_model_is_available() }
53 }
54
55 #[must_use]
57 pub fn availability() -> Availability {
58 let code = unsafe { ffi::fm_system_model_availability_code() };
59 availability_from_code(code)
60 }
61
62 #[must_use]
64 pub fn default_model() -> Option<ConfiguredSystemLanguageModel> {
65 let ptr = unsafe { ffi::fm_system_model_create_default() };
66 (!ptr.is_null()).then_some(ConfiguredSystemLanguageModel { ptr })
67 }
68
69 pub fn with_use_case(
75 use_case: UseCase,
76 guardrails: Guardrails,
77 ) -> Result<ConfiguredSystemLanguageModel, FMError> {
78 let mut error: *mut c_char = ptr::null_mut();
79 let ptr = unsafe {
80 ffi::fm_system_model_create(use_case.as_ffi(), guardrails.as_ffi(), &mut error)
81 };
82 if ptr.is_null() {
83 return Err(from_swift(ffi::status::MODEL_UNAVAILABLE, error));
84 }
85 Ok(ConfiguredSystemLanguageModel { ptr })
86 }
87
88 pub fn with_adapter(
95 adapter: &Adapter,
96 guardrails: Guardrails,
97 ) -> Result<ConfiguredSystemLanguageModel, FMError> {
98 let mut error: *mut c_char = ptr::null_mut();
99 let ptr = unsafe {
100 ffi::fm_system_model_create_with_adapter(adapter.ptr, guardrails.as_ffi(), &mut error)
101 };
102 if ptr.is_null() {
103 return Err(from_swift(ffi::status::MODEL_UNAVAILABLE, error));
104 }
105 Ok(ConfiguredSystemLanguageModel { ptr })
106 }
107
108 #[must_use]
110 pub fn supported_languages() -> Vec<String> {
111 let json = unsafe { ffi::fm_system_model_supported_languages_json(ptr::null_mut()) };
112 serde_json::from_str(&json_string(json)).unwrap_or_default()
113 }
114
115 #[must_use]
117 pub fn supports_locale(locale_identifier: &str) -> bool {
118 CString::new(locale_identifier).map_or(false, |locale| unsafe {
119 ffi::fm_system_model_supports_locale(ptr::null_mut(), locale.as_ptr())
120 })
121 }
122}
123
124pub struct ConfiguredSystemLanguageModel {
126 pub(crate) ptr: *mut c_void,
127}
128
129impl ConfiguredSystemLanguageModel {
130 #[must_use]
132 pub fn availability(&self) -> Availability {
133 availability_from_code(unsafe { ffi::fm_system_model_availability_code_for(self.ptr) })
134 }
135
136 #[must_use]
138 pub fn is_available(&self) -> bool {
139 matches!(self.availability(), Availability::Available)
140 }
141
142 #[must_use]
144 pub fn supported_languages(&self) -> Vec<String> {
145 let json = unsafe { ffi::fm_system_model_supported_languages_json(self.ptr) };
146 serde_json::from_str(&json_string(json)).unwrap_or_default()
147 }
148
149 #[must_use]
151 pub fn supports_locale(&self, locale_identifier: &str) -> bool {
152 CString::new(locale_identifier).map_or(false, |locale| unsafe {
153 ffi::fm_system_model_supports_locale(self.ptr, locale.as_ptr())
154 })
155 }
156}
157
158impl Drop for ConfiguredSystemLanguageModel {
159 fn drop(&mut self) {
160 if !self.ptr.is_null() {
161 unsafe { ffi::fm_object_release(self.ptr) };
162 }
163 }
164}
165
166impl core::fmt::Debug for ConfiguredSystemLanguageModel {
167 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
168 f.debug_struct("ConfiguredSystemLanguageModel")
169 .field("availability", &self.availability())
170 .finish()
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum UseCase {
177 General,
179 ContentTagging,
181}
182
183impl UseCase {
184 const fn as_ffi(self) -> i32 {
185 match self {
186 Self::General => 0,
187 Self::ContentTagging => 1,
188 }
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum Guardrails {
195 Default,
197 PermissiveContentTransformations,
199}
200
201impl Guardrails {
202 const fn as_ffi(self) -> i32 {
203 match self {
204 Self::Default => 0,
205 Self::PermissiveContentTransformations => 1,
206 }
207 }
208}
209
210pub struct Adapter {
212 pub(crate) ptr: *mut c_void,
213}
214
215impl Adapter {
216 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, FMError> {
222 let path = CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|error| {
223 FMError::InvalidArgument(format!(
224 "adapter path contains an interior NUL byte: {error}"
225 ))
226 })?;
227 let mut error: *mut c_char = ptr::null_mut();
228 let ptr = unsafe { ffi::fm_adapter_create_from_file(path.as_ptr(), &mut error) };
229 if ptr.is_null() {
230 return Err(from_swift(ffi::status::ADAPTER_INVALID_ASSET, error));
231 }
232 Ok(Self { ptr })
233 }
234
235 pub fn from_name(name: &str) -> Result<Self, FMError> {
241 let name = CString::new(name).map_err(|error| {
242 FMError::InvalidArgument(format!("adapter name contains NUL byte: {error}"))
243 })?;
244 let mut error: *mut c_char = ptr::null_mut();
245 let ptr = unsafe { ffi::fm_adapter_create_from_name(name.as_ptr(), &mut error) };
246 if ptr.is_null() {
247 return Err(from_swift(ffi::status::ADAPTER_INVALID_NAME, error));
248 }
249 Ok(Self { ptr })
250 }
251
252 pub fn compile(&self) -> Result<(), FMError> {
258 let (tx, rx) = mpsc::channel();
259 let tx_box: Box<mpsc::Sender<Result<(), FMError>>> = Box::new(tx);
260 let context = Box::into_raw(tx_box).cast::<c_void>();
261 unsafe { ffi::fm_adapter_compile(self.ptr, context, adapter_compile_trampoline) };
262 rx.recv().map_err(|_| FMError::Unknown {
263 code: ffi::status::UNKNOWN,
264 message: "Swift bridge dropped the adapter compile callback".into(),
265 })?
266 }
267
268 #[must_use]
270 pub fn creator_defined_metadata_json(&self) -> String {
271 let ptr = unsafe { ffi::fm_adapter_metadata_json(self.ptr) };
272 owned_string(ptr)
273 }
274
275 pub fn creator_defined_metadata(&self) -> Result<Value, FMError> {
277 serde_json::from_str(&self.creator_defined_metadata_json())
278 .map_err(|error| FMError::DecodingFailure(error.to_string()))
279 }
280
281 #[must_use]
283 pub fn compatible_adapter_identifiers(name: &str) -> Vec<String> {
284 let Ok(name) = CString::new(name) else {
285 return Vec::new();
286 };
287 let ptr = unsafe { ffi::fm_adapter_compatible_identifiers_json(name.as_ptr()) };
288 serde_json::from_str(&json_string(ptr)).unwrap_or_default()
289 }
290
291 pub fn remove_obsolete_adapters() -> Result<(), FMError> {
297 let mut error: *mut c_char = ptr::null_mut();
298 let status = unsafe { ffi::fm_adapter_remove_obsolete(&mut error) };
299 if status != ffi::status::OK {
300 return Err(from_swift(status, error));
301 }
302 Ok(())
303 }
304}
305
306impl Drop for Adapter {
307 fn drop(&mut self) {
308 if !self.ptr.is_null() {
309 unsafe { ffi::fm_object_release(self.ptr) };
310 }
311 }
312}
313
314impl core::fmt::Debug for Adapter {
315 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
316 f.debug_struct("Adapter").finish_non_exhaustive()
317 }
318}
319
320unsafe extern "C" fn adapter_compile_trampoline(
325 context: *mut c_void,
326 response: *mut c_char,
327 error: *mut c_char,
328 status: i32,
329) {
330 let tx = Box::from_raw(context.cast::<mpsc::Sender<Result<(), FMError>>>());
331 if !response.is_null() {
332 unsafe { ffi::fm_string_free(response) };
333 }
334 let result = if status == ffi::status::OK {
335 Ok(())
336 } else {
337 Err(from_swift(status, error))
338 };
339 let _ = tx.send(result);
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344#[non_exhaustive]
345pub enum Availability {
346 Available,
348 Unavailable(Unavailability),
350}