use crate::{InvocationContext, MultiToolPlugin, Tool, ToolCapabilities, ToolResult, ToolRuntime};
use serde::Serialize;
use std::ffi::c_void;
#[repr(C)]
pub struct CortexBuffer {
pub ptr: *mut u8,
pub len: usize,
pub cap: usize,
}
impl CortexBuffer {
#[must_use]
pub const fn empty() -> Self {
Self {
ptr: std::ptr::null_mut(),
len: 0,
cap: 0,
}
}
}
impl From<String> for CortexBuffer {
fn from(value: String) -> Self {
let mut bytes = value.into_bytes();
let buffer = Self {
ptr: bytes.as_mut_ptr(),
len: bytes.len(),
cap: bytes.capacity(),
};
std::mem::forget(bytes);
buffer
}
}
impl CortexBuffer {
pub const unsafe fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
if self.ptr.is_null() || self.len == 0 {
return Ok("");
}
let bytes = unsafe { std::slice::from_raw_parts(self.ptr.cast_const(), self.len) };
std::str::from_utf8(bytes)
}
}
pub unsafe extern "C" fn cortex_buffer_free(buffer: CortexBuffer) {
if buffer.ptr.is_null() {
return;
}
unsafe {
drop(Vec::from_raw_parts(buffer.ptr, buffer.len, buffer.cap));
}
}
#[repr(C)]
pub struct CortexHostApi {
pub abi_version: u32,
}
#[repr(C)]
pub struct CortexPluginApi {
pub abi_version: u32,
pub plugin: *mut c_void,
pub plugin_info: Option<unsafe extern "C" fn(*mut c_void) -> CortexBuffer>,
pub tool_count: Option<unsafe extern "C" fn(*mut c_void) -> usize>,
pub tool_descriptor: Option<unsafe extern "C" fn(*mut c_void, usize) -> CortexBuffer>,
pub tool_execute: Option<
unsafe extern "C" fn(*mut c_void, CortexBuffer, CortexBuffer, CortexBuffer) -> CortexBuffer,
>,
pub plugin_drop: Option<unsafe extern "C" fn(*mut c_void)>,
pub buffer_free: Option<unsafe extern "C" fn(CortexBuffer)>,
}
impl CortexPluginApi {
#[must_use]
pub const fn empty() -> Self {
Self {
abi_version: 0,
plugin: std::ptr::null_mut(),
plugin_info: None,
tool_count: None,
tool_descriptor: None,
tool_execute: None,
plugin_drop: None,
buffer_free: None,
}
}
}
#[derive(Serialize)]
struct ToolDescriptor<'a> {
name: &'a str,
description: &'a str,
input_schema: serde_json::Value,
timeout_secs: Option<u64>,
capabilities: ToolCapabilities,
}
struct NoopToolRuntime {
invocation: InvocationContext,
}
impl ToolRuntime for NoopToolRuntime {
fn invocation(&self) -> &InvocationContext {
&self.invocation
}
fn emit_progress(&self, _message: &str) {}
fn emit_observer(&self, _source: Option<&str>, _content: &str) {}
}
#[doc(hidden)]
pub struct NativePluginState {
plugin: Box<dyn MultiToolPlugin>,
tools: Vec<Box<dyn Tool>>,
}
impl NativePluginState {
#[must_use]
pub fn new(plugin: Box<dyn MultiToolPlugin>) -> Self {
let tools = plugin.create_tools();
Self { plugin, tools }
}
}
fn json_buffer<T: Serialize>(value: &T) -> CortexBuffer {
match serde_json::to_string(value) {
Ok(json) => CortexBuffer::from(json),
Err(err) => CortexBuffer::from(
serde_json::json!({
"output": format!("native ABI serialization error: {err}"),
"media": [],
"is_error": true
})
.to_string(),
),
}
}
#[doc(hidden)]
pub unsafe extern "C" fn native_plugin_info(state: *mut c_void) -> CortexBuffer {
if state.is_null() {
return CortexBuffer::empty();
}
let state = unsafe { &*state.cast::<NativePluginState>() };
json_buffer(&state.plugin.plugin_info())
}
#[doc(hidden)]
pub unsafe extern "C" fn native_tool_count(state: *mut c_void) -> usize {
if state.is_null() {
return 0;
}
let state = unsafe { &*state.cast::<NativePluginState>() };
state.tools.len()
}
#[doc(hidden)]
pub unsafe extern "C" fn native_tool_descriptor(state: *mut c_void, index: usize) -> CortexBuffer {
if state.is_null() {
return CortexBuffer::empty();
}
let state = unsafe { &*state.cast::<NativePluginState>() };
let Some(tool) = state.tools.get(index) else {
return CortexBuffer::empty();
};
let descriptor = ToolDescriptor {
name: tool.name(),
description: tool.description(),
input_schema: tool.input_schema(),
timeout_secs: tool.timeout_secs(),
capabilities: tool.capabilities(),
};
json_buffer(&descriptor)
}
#[doc(hidden)]
pub unsafe extern "C" fn native_tool_execute(
state: *mut c_void,
tool_name: CortexBuffer,
input_json: CortexBuffer,
invocation_json: CortexBuffer,
) -> CortexBuffer {
if state.is_null() {
return json_buffer(&ToolResult::error("native plugin state is null"));
}
let tool_name = match unsafe { tool_name.as_str() } {
Ok(value) => value,
Err(err) => return json_buffer(&ToolResult::error(format!("invalid tool name: {err}"))),
};
let input_json = match unsafe { input_json.as_str() } {
Ok(value) => value,
Err(err) => return json_buffer(&ToolResult::error(format!("invalid input JSON: {err}"))),
};
let invocation_json = match unsafe { invocation_json.as_str() } {
Ok(value) => value,
Err(err) => {
return json_buffer(&ToolResult::error(format!(
"invalid invocation JSON: {err}"
)));
}
};
let input = match serde_json::from_str(input_json) {
Ok(value) => value,
Err(err) => return json_buffer(&ToolResult::error(format!("invalid input JSON: {err}"))),
};
let invocation = match serde_json::from_str(invocation_json) {
Ok(value) => value,
Err(err) => {
return json_buffer(&ToolResult::error(format!(
"invalid invocation JSON: {err}"
)));
}
};
let state = unsafe { &*state.cast::<NativePluginState>() };
let Some(tool) = state.tools.iter().find(|tool| tool.name() == tool_name) else {
return json_buffer(&ToolResult::error(format!(
"native plugin does not expose tool '{tool_name}'"
)));
};
let runtime = NoopToolRuntime { invocation };
match tool.execute_with_runtime(input, &runtime) {
Ok(result) => json_buffer(&result),
Err(err) => json_buffer(&ToolResult::error(format!("tool error: {err}"))),
}
}
#[doc(hidden)]
pub unsafe extern "C" fn native_plugin_drop(state: *mut c_void) {
if state.is_null() {
return;
}
unsafe {
drop(Box::from_raw(state.cast::<NativePluginState>()));
}
}