1use std::ffi::{c_char, CStr, NulError};
9
10use llama_cpp_sys_4 as sys;
11
12use crate::token::LlamaToken;
13
14#[derive(Debug, thiserror::Error)]
20pub enum ShimError {
21 #[error("argument contained an interior NUL byte")]
25 Nul(#[from] NulError),
26 #[error("invalid argument passed to the shim")]
28 InvalidArg,
29 #[error("input was not valid JSON: {0}")]
31 BadJson(String),
32 #[error("llama.cpp call failed: {0}")]
34 Failed(String),
35 #[error("could not construct: {0}")]
37 Init(String),
38 #[error("llama.cpp returned non-UTF-8 output")]
40 Utf8(#[from] std::string::FromUtf8Error),
41 #[error("shim reported an out-of-range offset")]
44 CorruptResult,
45}
46
47pub(crate) type Result<T> = std::result::Result<T, ShimError>;
48
49pub(crate) fn last_error() -> String {
53 let ptr = unsafe { sys::llama_shim_last_error() };
54 if ptr.is_null() {
55 return String::new();
56 }
57 unsafe { CStr::from_ptr(ptr) }
58 .to_string_lossy()
59 .into_owned()
60}
61
62pub(crate) fn check_status(status: i32) -> Result<()> {
64 match status {
65 sys::LLAMA_SHIM_OK => Ok(()),
66 sys::LLAMA_SHIM_INVALID_ARG => Err(ShimError::InvalidArg),
67 sys::LLAMA_SHIM_BAD_JSON => Err(ShimError::BadJson(last_error())),
68 _ => Err(ShimError::Failed(last_error())),
69 }
70}
71
72pub(crate) fn read_string<F>(mut call: F) -> Result<String>
77where
78 F: FnMut(*mut c_char, usize, *mut usize) -> i32,
79{
80 let mut needed: usize = 0;
81 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
82 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
83 check_status(status)?;
84 }
85 if needed == 0 {
86 return Ok(String::new());
87 }
88
89 let mut buf = vec![0u8; needed];
90 let status = call(buf.as_mut_ptr().cast::<c_char>(), buf.len(), &raw mut needed);
91 check_status(status)?;
92
93 let end = buf.iter().position(|b| *b == 0).unwrap_or(buf.len());
96 buf.truncate(end);
97 String::from_utf8(buf).map_err(ShimError::from)
98}
99
100pub(crate) fn read_tokens<F>(mut call: F) -> Result<Vec<LlamaToken>>
102where
103 F: FnMut(*mut i32, usize, *mut usize) -> i32,
104{
105 let mut needed: usize = 0;
106 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
107 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
108 check_status(status)?;
109 }
110 if needed == 0 {
111 return Ok(Vec::new());
112 }
113
114 let mut buf = vec![0i32; needed];
115 let status = call(buf.as_mut_ptr(), buf.len(), &raw mut needed);
116 check_status(status)?;
117 buf.truncate(needed);
118 Ok(buf.into_iter().map(LlamaToken).collect())
119}
120
121pub(crate) fn read_i32s<F>(mut call: F) -> Result<Vec<i32>>
124where
125 F: FnMut(*mut i32, usize, *mut usize) -> i32,
126{
127 let mut needed: usize = 0;
128 let status = call(std::ptr::null_mut(), 0, &raw mut needed);
129 if status != sys::LLAMA_SHIM_BUFFER_TOO_SMALL {
130 check_status(status)?;
131 }
132 if needed == 0 {
133 return Ok(Vec::new());
134 }
135
136 let mut buf = vec![0i32; needed];
137 let status = call(buf.as_mut_ptr(), buf.len(), &raw mut needed);
138 check_status(status)?;
139 buf.truncate(needed);
140 Ok(buf)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
151 fn status_codes_are_distinct_and_signed_as_documented() {
152 assert_eq!(sys::LLAMA_SHIM_OK, 0);
155 assert_eq!(
156 sys::LLAMA_SHIM_BUFFER_TOO_SMALL, 1,
157 "a size query is not a failure, so it must be positive"
158 );
159 assert_eq!(sys::LLAMA_SHIM_INVALID_ARG, -1);
160 assert_eq!(sys::LLAMA_SHIM_BAD_JSON, -2);
161 assert_eq!(sys::LLAMA_SHIM_THROWN, -3);
162 }
163
164 #[test]
165 fn check_status_maps_each_code() {
166 assert!(check_status(sys::LLAMA_SHIM_OK).is_ok());
167 assert!(matches!(
168 check_status(sys::LLAMA_SHIM_INVALID_ARG),
169 Err(ShimError::InvalidArg)
170 ));
171 assert!(matches!(
172 check_status(sys::LLAMA_SHIM_BAD_JSON),
173 Err(ShimError::BadJson(_))
174 ));
175 assert!(matches!(
176 check_status(sys::LLAMA_SHIM_THROWN),
177 Err(ShimError::Failed(_))
178 ));
179 }
180
181 #[test]
184 fn last_error_is_empty_before_any_failure() {
185 let _ = last_error();
188 }
189}