1use crate::{InvocationContext, MultiToolPlugin, Tool, ToolCapabilities, ToolResult, ToolRuntime};
2use serde::Serialize;
3use std::ffi::c_void;
4
5#[repr(C)]
11pub struct CortexBuffer {
12 pub ptr: *mut u8,
14 pub len: usize,
16 pub cap: usize,
18}
19
20impl CortexBuffer {
21 #[must_use]
22 pub const fn empty() -> Self {
23 Self {
24 ptr: std::ptr::null_mut(),
25 len: 0,
26 cap: 0,
27 }
28 }
29}
30
31impl From<String> for CortexBuffer {
32 fn from(value: String) -> Self {
33 let mut bytes = value.into_bytes();
34 let buffer = Self {
35 ptr: bytes.as_mut_ptr(),
36 len: bytes.len(),
37 cap: bytes.capacity(),
38 };
39 std::mem::forget(bytes);
40 buffer
41 }
42}
43
44impl CortexBuffer {
45 pub const unsafe fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
54 if self.ptr.is_null() || self.len == 0 {
55 return Ok("");
56 }
57 let bytes = unsafe { std::slice::from_raw_parts(self.ptr.cast_const(), self.len) };
59 std::str::from_utf8(bytes)
60 }
61}
62
63pub unsafe extern "C" fn cortex_buffer_free(buffer: CortexBuffer) {
69 if buffer.ptr.is_null() {
70 return;
71 }
72 unsafe {
74 drop(Vec::from_raw_parts(buffer.ptr, buffer.len, buffer.cap));
75 }
76}
77
78#[repr(C)]
80pub struct CortexHostApi {
81 pub abi_version: u32,
83}
84
85#[repr(C)]
87pub struct CortexPluginApi {
88 pub abi_version: u32,
90 pub plugin: *mut c_void,
92 pub plugin_info: Option<unsafe extern "C" fn(*mut c_void) -> CortexBuffer>,
94 pub tool_count: Option<unsafe extern "C" fn(*mut c_void) -> usize>,
96 pub tool_descriptor: Option<unsafe extern "C" fn(*mut c_void, usize) -> CortexBuffer>,
98 pub tool_execute: Option<
101 unsafe extern "C" fn(*mut c_void, CortexBuffer, CortexBuffer, CortexBuffer) -> CortexBuffer,
102 >,
103 pub plugin_drop: Option<unsafe extern "C" fn(*mut c_void)>,
105 pub buffer_free: Option<unsafe extern "C" fn(CortexBuffer)>,
107}
108
109impl CortexPluginApi {
110 #[must_use]
111 pub const fn empty() -> Self {
112 Self {
113 abi_version: 0,
114 plugin: std::ptr::null_mut(),
115 plugin_info: None,
116 tool_count: None,
117 tool_descriptor: None,
118 tool_execute: None,
119 plugin_drop: None,
120 buffer_free: None,
121 }
122 }
123}
124
125#[derive(Serialize)]
126struct ToolDescriptor<'a> {
127 name: &'a str,
128 description: &'a str,
129 input_schema: serde_json::Value,
130 timeout_secs: Option<u64>,
131 capabilities: ToolCapabilities,
132}
133
134struct NoopToolRuntime {
135 invocation: InvocationContext,
136}
137
138impl ToolRuntime for NoopToolRuntime {
139 fn invocation(&self) -> &InvocationContext {
140 &self.invocation
141 }
142
143 fn emit_progress(&self, _message: &str) {}
144
145 fn emit_observer(&self, _source: Option<&str>, _content: &str) {}
146}
147
148#[doc(hidden)]
149pub struct NativePluginState {
150 plugin: Box<dyn MultiToolPlugin>,
151 tools: Vec<Box<dyn Tool>>,
152}
153
154impl NativePluginState {
155 #[must_use]
156 pub fn new(plugin: Box<dyn MultiToolPlugin>) -> Self {
157 let tools = plugin.create_tools();
158 Self { plugin, tools }
159 }
160}
161
162fn json_buffer<T: Serialize>(value: &T) -> CortexBuffer {
163 match serde_json::to_string(value) {
164 Ok(json) => CortexBuffer::from(json),
165 Err(err) => CortexBuffer::from(
166 serde_json::json!({
167 "output": format!("native ABI serialization error: {err}"),
168 "media": [],
169 "is_error": true
170 })
171 .to_string(),
172 ),
173 }
174}
175
176#[doc(hidden)]
177pub unsafe extern "C" fn native_plugin_info(state: *mut c_void) -> CortexBuffer {
178 if state.is_null() {
179 return CortexBuffer::empty();
180 }
181 let state = unsafe { &*state.cast::<NativePluginState>() };
184 json_buffer(&state.plugin.plugin_info())
185}
186
187#[doc(hidden)]
188pub unsafe extern "C" fn native_tool_count(state: *mut c_void) -> usize {
189 if state.is_null() {
190 return 0;
191 }
192 let state = unsafe { &*state.cast::<NativePluginState>() };
194 state.tools.len()
195}
196
197#[doc(hidden)]
198pub unsafe extern "C" fn native_tool_descriptor(state: *mut c_void, index: usize) -> CortexBuffer {
199 if state.is_null() {
200 return CortexBuffer::empty();
201 }
202 let state = unsafe { &*state.cast::<NativePluginState>() };
204 let Some(tool) = state.tools.get(index) else {
205 return CortexBuffer::empty();
206 };
207 let descriptor = ToolDescriptor {
208 name: tool.name(),
209 description: tool.description(),
210 input_schema: tool.input_schema(),
211 timeout_secs: tool.timeout_secs(),
212 capabilities: tool.capabilities(),
213 };
214 json_buffer(&descriptor)
215}
216
217#[doc(hidden)]
218pub unsafe extern "C" fn native_tool_execute(
219 state: *mut c_void,
220 tool_name: CortexBuffer,
221 input_json: CortexBuffer,
222 invocation_json: CortexBuffer,
223) -> CortexBuffer {
224 if state.is_null() {
225 return json_buffer(&ToolResult::error("native plugin state is null"));
226 }
227 let tool_name = match unsafe { tool_name.as_str() } {
229 Ok(value) => value,
230 Err(err) => return json_buffer(&ToolResult::error(format!("invalid tool name: {err}"))),
231 };
232 let input_json = match unsafe { input_json.as_str() } {
234 Ok(value) => value,
235 Err(err) => return json_buffer(&ToolResult::error(format!("invalid input JSON: {err}"))),
236 };
237 let invocation_json = match unsafe { invocation_json.as_str() } {
239 Ok(value) => value,
240 Err(err) => {
241 return json_buffer(&ToolResult::error(format!(
242 "invalid invocation JSON: {err}"
243 )));
244 }
245 };
246 let input = match serde_json::from_str(input_json) {
247 Ok(value) => value,
248 Err(err) => return json_buffer(&ToolResult::error(format!("invalid input JSON: {err}"))),
249 };
250 let invocation = match serde_json::from_str(invocation_json) {
251 Ok(value) => value,
252 Err(err) => {
253 return json_buffer(&ToolResult::error(format!(
254 "invalid invocation JSON: {err}"
255 )));
256 }
257 };
258 let state = unsafe { &*state.cast::<NativePluginState>() };
260 let Some(tool) = state.tools.iter().find(|tool| tool.name() == tool_name) else {
261 return json_buffer(&ToolResult::error(format!(
262 "native plugin does not expose tool '{tool_name}'"
263 )));
264 };
265 let runtime = NoopToolRuntime { invocation };
266 match tool.execute_with_runtime(input, &runtime) {
267 Ok(result) => json_buffer(&result),
268 Err(err) => json_buffer(&ToolResult::error(format!("tool error: {err}"))),
269 }
270}
271
272#[doc(hidden)]
273pub unsafe extern "C" fn native_plugin_drop(state: *mut c_void) {
274 if state.is_null() {
275 return;
276 }
277 unsafe {
280 drop(Box::from_raw(state.cast::<NativePluginState>()));
281 }
282}