1use std::ffi::CStr;
4use std::ptr::{self, NonNull};
5
6use crate::error::{Error, Result};
7use crate::ffi::{self, AvailabilityCode, SwiftPtr};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ModelAvailability {
12 Available,
14 DeviceNotEligible,
16 AppleIntelligenceNotEnabled,
18 ModelNotReady,
20 Unknown,
22}
23
24impl ModelAvailability {
25 pub fn into_error(self) -> Option<Error> {
27 match self {
28 ModelAvailability::Available => None,
29 ModelAvailability::DeviceNotEligible => Some(Error::DeviceNotEligible),
30 ModelAvailability::AppleIntelligenceNotEnabled => {
31 Some(Error::AppleIntelligenceNotEnabled)
32 }
33 ModelAvailability::ModelNotReady => Some(Error::ModelNotReady),
34 ModelAvailability::Unknown => Some(Error::ModelNotAvailable),
35 }
36 }
37}
38
39impl From<AvailabilityCode> for ModelAvailability {
40 fn from(code: AvailabilityCode) -> Self {
41 match code {
42 AvailabilityCode::Available => ModelAvailability::Available,
43 AvailabilityCode::DeviceNotEligible => ModelAvailability::DeviceNotEligible,
44 AvailabilityCode::AppleIntelligenceNotEnabled => {
45 ModelAvailability::AppleIntelligenceNotEnabled
46 }
47 AvailabilityCode::ModelNotReady => ModelAvailability::ModelNotReady,
48 AvailabilityCode::Unknown => ModelAvailability::Unknown,
49 }
50 }
51}
52
53pub struct SystemLanguageModel {
70 ptr: NonNull<std::ffi::c_void>,
71}
72
73impl SystemLanguageModel {
74 pub fn new() -> Result<Self> {
81 let mut error: SwiftPtr = ptr::null_mut();
82
83 let ptr = unsafe { ffi::fm_model_default(&raw mut error) };
84
85 if !error.is_null() {
86 return Err(error_from_swift(error));
87 }
88
89 NonNull::new(ptr).map(|ptr| Self { ptr }).ok_or_else(|| {
90 Error::InternalError(
91 "SystemLanguageModel creation returned null without error. \
92 This may indicate FoundationModels.framework is unavailable."
93 .to_string(),
94 )
95 })
96 }
97
98 pub(crate) fn as_ptr(&self) -> SwiftPtr {
102 self.ptr.as_ptr()
103 }
104
105 pub fn is_available(&self) -> bool {
109 unsafe { ffi::fm_model_is_available(self.ptr.as_ptr()) }
110 }
111
112 pub fn availability(&self) -> ModelAvailability {
116 let code = unsafe { ffi::fm_model_availability(self.ptr.as_ptr()) };
117 AvailabilityCode::from(code).into()
118 }
119
120 pub fn ensure_available(&self) -> Result<()> {
122 match self.availability().into_error() {
123 Some(err) => Err(err),
124 None => Ok(()),
125 }
126 }
127}
128
129impl std::fmt::Debug for SystemLanguageModel {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("SystemLanguageModel")
132 .field("availability", &self.availability())
133 .finish()
134 }
135}
136
137impl Drop for SystemLanguageModel {
138 fn drop(&mut self) {
139 unsafe {
140 ffi::fm_model_free(self.ptr.as_ptr());
141 }
142 }
143}
144
145unsafe impl Send for SystemLanguageModel {}
148unsafe impl Sync for SystemLanguageModel {}
149
150pub(crate) fn error_from_swift(error: SwiftPtr) -> Error {
152 use crate::error::ToolCallError;
153
154 if error.is_null() {
155 return Error::InternalError(
156 "FFI error object was null; unable to retrieve error details".to_string(),
157 );
158 }
159
160 let code = unsafe { ffi::fm_error_code(error) };
161 let msg_ptr = unsafe { ffi::fm_error_message(error) };
162
163 let message = if msg_ptr.is_null() {
164 "Error message unavailable (null pointer from Swift)".to_string()
165 } else {
166 unsafe { CStr::from_ptr(msg_ptr).to_string_lossy().into_owned() }
167 };
168
169 let tool_name = unsafe {
171 let ptr = ffi::fm_error_tool_name(error);
172 if ptr.is_null() {
173 None
174 } else {
175 Some(CStr::from_ptr(ptr).to_string_lossy().into_owned())
176 }
177 };
178
179 let tool_arguments = unsafe {
180 let ptr = ffi::fm_error_tool_arguments(error);
181 if ptr.is_null() {
182 None
183 } else {
184 let json_str = CStr::from_ptr(ptr).to_string_lossy().into_owned();
185 serde_json::from_str(&json_str).ok()
186 }
187 };
188
189 unsafe {
190 ffi::fm_error_free(error);
191 }
192
193 match ffi::ErrorCode::from(code) {
194 ffi::ErrorCode::ModelNotAvailable => Error::ModelNotAvailable,
195 ffi::ErrorCode::GenerationFailed => Error::GenerationError(message),
196 ffi::ErrorCode::Cancelled => Error::GenerationError("Operation cancelled".to_string()),
197 ffi::ErrorCode::Timeout => Error::Timeout(message),
198 ffi::ErrorCode::ToolError => {
199 Error::ToolCall(ToolCallError {
201 tool_name: tool_name.unwrap_or_else(|| "unknown".to_string()),
202 arguments: tool_arguments.unwrap_or(serde_json::Value::Null),
203 inner_error: message,
204 })
205 }
206 ffi::ErrorCode::InvalidInput => Error::InvalidInput(message),
207 ffi::ErrorCode::Unknown => Error::InternalError(message),
208 }
209}