#![allow(clippy::missing_safety_doc)]
use crate::db::AgentDB;
use crate::hybrid::{HybridQuery, TriModalQuery};
use crate::vectors::{DistanceMetric, SearchOptions, VectorEntry};
use serde_json::Value;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
thread_local! {
static LAST_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
}
fn set_last_error(msg: impl Into<String>) {
LAST_ERROR.with(|e| *e.borrow_mut() = Some(msg.into()));
}
fn clear_last_error() {
LAST_ERROR.with(|e| *e.borrow_mut() = None);
}
#[no_mangle]
pub extern "C" fn agentdb_last_error() -> *mut c_char {
LAST_ERROR.with(|e| match e.borrow().as_deref() {
Some(msg) => CString::new(msg)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
None => std::ptr::null_mut(),
})
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
let _ = CString::from_raw(ptr);
}
}
pub struct AgentDbHandle {
db: AgentDB,
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_open(path: *const c_char) -> *mut AgentDbHandle {
clear_last_error();
let path_str = match path.as_ref().and_then(|p| CStr::from_ptr(p).to_str().ok()) {
Some(s) => s,
None => {
set_last_error("agentdb_open: invalid path string");
return std::ptr::null_mut();
}
};
match AgentDB::open(path_str) {
Ok(db) => Box::into_raw(Box::new(AgentDbHandle { db })),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_close(handle: *mut AgentDbHandle) {
if !handle.is_null() {
let _ = Box::from_raw(handle);
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_execute(handle: *mut AgentDbHandle, sql: *const c_char) -> i64 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("agentdb_execute: null handle");
return -1;
}
};
let sql_str = match CStr::from_ptr(sql).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("agentdb_execute: invalid SQL string");
return -1;
}
};
match h.db.execute(sql_str) {
Ok(n) => n as i64,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_query_json(
handle: *mut AgentDbHandle,
sql: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("agentdb_query_json: null handle");
return std::ptr::null_mut();
}
};
let sql_str = match CStr::from_ptr(sql).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("agentdb_query_json: invalid SQL");
return std::ptr::null_mut();
}
};
match h.db.query_json(sql_str) {
Ok(rows) => {
let json = Value::Array(rows).to_string();
CString::new(json)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_query_json_params(
handle: *mut AgentDbHandle,
sql: *const c_char,
params_json: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("agentdb_query_json_params: null handle");
return std::ptr::null_mut();
}
};
let sql_str = match CStr::from_ptr(sql).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("agentdb_query_json_params: invalid SQL");
return std::ptr::null_mut();
}
};
let params: Vec<String> = if params_json.is_null() {
vec![]
} else {
match CStr::from_ptr(params_json).to_str() {
Ok(s) => serde_json::from_str::<Vec<serde_json::Value>>(s)
.unwrap_or_default()
.iter()
.map(|v| match v {
serde_json::Value::String(s) => s.clone(),
other => other.to_string(),
})
.collect(),
Err(_) => {
set_last_error("agentdb_query_json_params: invalid params");
return std::ptr::null_mut();
}
}
};
let param_refs: Vec<&dyn rusqlite::ToSql> =
params.iter().map(|s| s as &dyn rusqlite::ToSql).collect();
match h.db.query_json_params(sql_str, ¶m_refs) {
Ok(rows) => {
let json = Value::Array(rows).to_string();
CString::new(json)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_vector_upsert(
handle: *mut AgentDbHandle,
collection: *const c_char,
id: *const c_char,
vector: *const f32,
dim: usize,
metadata: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let col_name = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection name");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
let vec: Vec<f32> = std::slice::from_raw_parts(vector, dim).to_vec();
let meta: Option<Value> = if metadata.is_null() {
None
} else {
CStr::from_ptr(metadata)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let col = match h.db.vectors().collection(col_name, dim) {
Ok(c) => c,
Err(e) => {
set_last_error(e.to_string());
return -1;
}
};
match col.upsert(VectorEntry {
id: id_str.to_string(),
vector: vec,
metadata: meta,
}) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_vector_search(
handle: *mut AgentDbHandle,
collection: *const c_char,
query: *const f32,
dim: usize,
top_k: usize,
filter_json: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let col_name = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection name");
return std::ptr::null_mut();
}
};
let q: Vec<f32> = std::slice::from_raw_parts(query, dim).to_vec();
let filter: Option<Value> = if filter_json.is_null() {
None
} else {
CStr::from_ptr(filter_json)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let col = match h.db.vectors().collection(col_name, dim) {
Ok(c) => c,
Err(e) => {
set_last_error(e.to_string());
return std::ptr::null_mut();
}
};
match col.search(
&q,
SearchOptions {
top_k,
metric: DistanceMetric::Cosine,
filter,
},
) {
Ok(results) => {
let json: Vec<Value> = results
.iter()
.map(
|r| serde_json::json!({ "id": r.id, "score": r.score, "metadata": r.metadata }),
)
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_graph_add_node(
handle: *mut AgentDbHandle,
id: *const c_char,
kind: *const c_char,
data_json: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
let kind_str = match CStr::from_ptr(kind).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid kind");
return -1;
}
};
let data: Option<Value> = if data_json.is_null() {
None
} else {
CStr::from_ptr(data_json)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
match h.db.memory().add_node(id_str, kind_str, data) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_graph_add_edge(
handle: *mut AgentDbHandle,
src: *const c_char,
dst: *const c_char,
relation: *const c_char,
weight: f64,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let src_str = match CStr::from_ptr(src).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid src");
return -1;
}
};
let dst_str = match CStr::from_ptr(dst).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid dst");
return -1;
}
};
let relation_str = match CStr::from_ptr(relation).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid relation");
return -1;
}
};
match h
.db
.memory()
.add_edge(src_str, dst_str, relation_str, weight)
{
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_graph_neighbors(
handle: *mut AgentDbHandle,
node_id: *const c_char,
max_depth: usize,
min_weight: f64,
relation: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let id_str = match CStr::from_ptr(node_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid node_id");
return std::ptr::null_mut();
}
};
let relation_str: Option<String> = if relation.is_null() {
None
} else {
CStr::from_ptr(relation)
.to_str()
.ok()
.map(|s| s.to_string())
};
let opts = crate::memory::TraversalOptions {
relation: relation_str,
max_depth,
min_weight: Some(min_weight),
};
match h.db.memory().neighbors(id_str, opts) {
Ok(results) => {
let json: Vec<Value> = results
.iter()
.map(|r| {
serde_json::json!({
"id": r.node.id,
"kind": r.node.kind,
"depth": r.depth,
"weight": r.weight,
"data": r.node.data
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_fts_index(
handle: *mut AgentDbHandle,
collection: *const c_char,
vec_id: *const c_char,
collection_id: *const c_char,
text: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let col = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection");
return -1;
}
};
let vid = match CStr::from_ptr(vec_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid vec_id");
return -1;
}
};
let cid = match CStr::from_ptr(collection_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection_id");
return -1;
}
};
let txt = match CStr::from_ptr(text).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid text");
return -1;
}
};
match h.db.fts().index_text(col, vid, cid, txt) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_fts_search(
handle: *mut AgentDbHandle,
collection: *const c_char,
query: *const c_char,
top_k: usize,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let col = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection");
return std::ptr::null_mut();
}
};
let q = match CStr::from_ptr(query).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid query");
return std::ptr::null_mut();
}
};
match h.db.fts().search(col, q, top_k) {
Ok(results) => {
let json: Vec<Value> = results
.iter()
.map(|r| serde_json::json!({ "id": r.id, "snippet": r.snippet, "rank": r.rank }))
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_hybrid_query(
handle: *mut AgentDbHandle,
anchor_node: *const c_char,
embedding: *const f32,
dim: usize,
collection: *const c_char,
graph_depth: usize,
top_k: usize,
alpha: f64,
filter_json: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let anchor = match CStr::from_ptr(anchor_node).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid anchor_node");
return std::ptr::null_mut();
}
};
let col = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection");
return std::ptr::null_mut();
}
};
let emb: Vec<f32> = std::slice::from_raw_parts(embedding, dim).to_vec();
let filter: Option<serde_json::Value> = if filter_json.is_null() {
None
} else {
CStr::from_ptr(filter_json)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let q = HybridQuery {
anchor_node: anchor,
embedding: &emb,
collection: col,
graph_depth,
top_k,
alpha,
filter,
};
match h.db.hybrid_query(q) {
Ok(results) => {
let json: Vec<Value> = results
.iter()
.map(|r| {
serde_json::json!({
"id": r.id,
"rank_score": r.rank_score,
"vector_score": r.vector_score,
"graph_weight": r.graph_weight
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_tri_modal_query(
handle: *mut AgentDbHandle,
query_json: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let json_str = match CStr::from_ptr(query_json).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("agentdb_tri_modal_query: invalid query_json");
return std::ptr::null_mut();
}
};
let obj: serde_json::Value = match serde_json::from_str(json_str) {
Ok(v) => v,
Err(e) => {
set_last_error(format!("agentdb_tri_modal_query: parse error: {e}"));
return std::ptr::null_mut();
}
};
let anchor_node = obj["anchor_node"]
.as_str()
.unwrap_or("")
.to_string();
let embedding: Vec<f32> = obj["embedding"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_f64().map(|f| f as f32))
.collect()
})
.unwrap_or_default();
let text_query = obj["text_query"].as_str().unwrap_or("").to_string();
let collection = obj["collection"].as_str().unwrap_or("").to_string();
let graph_depth = obj["graph_depth"].as_u64().unwrap_or(3) as usize;
let top_k = obj["top_k"].as_u64().unwrap_or(10) as usize;
let alpha = obj["alpha"].as_f64().unwrap_or(0.33) as f32;
let beta = obj["beta"].as_f64().unwrap_or(0.33) as f32;
let gamma = obj["gamma"].as_f64().unwrap_or(0.34) as f32;
let filter: Option<serde_json::Value> = obj.get("filter").cloned().and_then(|v| {
if v.is_null() { None } else { Some(v) }
});
let q = TriModalQuery {
anchor_node,
embedding,
text_query,
collection,
graph_depth,
top_k,
alpha,
beta,
gamma,
filter,
};
match h.db.tri_modal_query(&q) {
Ok(results) => {
let json: Vec<serde_json::Value> = results
.iter()
.map(|r| {
serde_json::json!({
"id": r.id,
"rank_score": r.rank_score,
"vector_score": r.vector_score,
"graph_weight": r.graph_weight,
"fts_rank": r.fts_rank,
"metadata": r.metadata
})
})
.collect();
let s = serde_json::Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_stats(handle: *mut AgentDbHandle) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
match h.db.stats() {
Ok(s) => {
let json = serde_json::json!({
"collections": s.collections,
"vectors": s.vectors,
"nodes": s.nodes,
"edges": s.edges,
"conversations": s.conversations,
"messages": s.messages,
"workflows": s.workflows,
"workflow_steps": s.workflow_steps,
"traces": s.traces,
"tools": s.tools,
"tool_calls": s.tool_calls,
"audit_entries": s.audit_entries,
"prompt_templates": s.prompt_templates
});
CString::new(json.to_string())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_conversation_create(
handle: *mut AgentDbHandle,
id: *const c_char,
title: *const c_char,
metadata: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
let title_opt = if title.is_null() {
None
} else {
CStr::from_ptr(title).to_str().ok()
};
let meta: Option<Value> = if metadata.is_null() {
None
} else {
CStr::from_ptr(metadata)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
match h
.db
.conversations()
.create_conversation(id_str, title_opt, meta)
{
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_conversation_add_message(
handle: *mut AgentDbHandle,
conversation_id: *const c_char,
role: *const c_char,
content: *const c_char,
metadata: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let cid = match CStr::from_ptr(conversation_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid conversation_id");
return std::ptr::null_mut();
}
};
let role_str = match CStr::from_ptr(role).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid role");
return std::ptr::null_mut();
}
};
let content_str = match CStr::from_ptr(content).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid content");
return std::ptr::null_mut();
}
};
let meta: Option<Value> = if metadata.is_null() {
None
} else {
CStr::from_ptr(metadata)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
match h
.db
.conversations()
.add_message(cid, role_str, content_str, meta)
{
Ok(msg_id) => CString::new(msg_id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_conversation_get_messages(
handle: *mut AgentDbHandle,
conversation_id: *const c_char,
limit: usize,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let cid = match CStr::from_ptr(conversation_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid conversation_id");
return std::ptr::null_mut();
}
};
let lim = if limit == 0 { None } else { Some(limit) };
match h.db.conversations().get_messages(cid, lim) {
Ok(msgs) => {
let json: Vec<Value> = msgs
.iter()
.map(|m| {
serde_json::json!({
"id": m.id,
"conversation_id": m.conversation_id,
"role": m.role,
"content": m.content,
"metadata": m.metadata,
"created_at": m.created_at
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_conversation_list(handle: *mut AgentDbHandle) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
match h.db.conversations().list_conversations() {
Ok(convos) => {
let json: Vec<Value> = convos
.iter()
.map(|c| {
serde_json::json!({
"id": c.id,
"title": c.title,
"metadata": c.metadata,
"created_at": c.created_at,
"updated_at": c.updated_at
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_conversation_delete(
handle: *mut AgentDbHandle,
id: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
match h.db.conversations().delete_conversation(id_str) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_workflow_create(
handle: *mut AgentDbHandle,
id: *const c_char,
name: *const c_char,
input: *const c_char,
metadata: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
let name_str = match CStr::from_ptr(name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid name");
return -1;
}
};
let input_val: Option<Value> = if input.is_null() {
None
} else {
CStr::from_ptr(input)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let metadata_val: Option<Value> = if metadata.is_null() {
None
} else {
CStr::from_ptr(metadata)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
match h
.db
.workflows()
.create_workflow(id_str, name_str, input_val, metadata_val)
{
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_workflow_add_step(
handle: *mut AgentDbHandle,
workflow_id: *const c_char,
name: *const c_char,
input: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let wid = match CStr::from_ptr(workflow_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid workflow_id");
return std::ptr::null_mut();
}
};
let name_str = match CStr::from_ptr(name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid name");
return std::ptr::null_mut();
}
};
let input_val: Option<Value> = if input.is_null() {
None
} else {
CStr::from_ptr(input)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
match h.db.workflows().add_step(wid, name_str, input_val) {
Ok(step_id) => CString::new(step_id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_workflow_update_step(
handle: *mut AgentDbHandle,
step_id: *const c_char,
status: *const c_char,
output: *const c_char,
error: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let sid = match CStr::from_ptr(step_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid step_id");
return -1;
}
};
let status_str = match CStr::from_ptr(status).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid status");
return -1;
}
};
let output_val: Option<Value> = if output.is_null() {
None
} else {
CStr::from_ptr(output)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let error_str = if error.is_null() {
None
} else {
CStr::from_ptr(error).to_str().ok()
};
match h
.db
.workflows()
.update_step(sid, status_str, output_val, error_str)
{
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_workflow_complete(
handle: *mut AgentDbHandle,
id: *const c_char,
output: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
let output_val: Option<Value> = if output.is_null() {
None
} else {
CStr::from_ptr(output)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
match h.db.workflows().complete_workflow(id_str, output_val) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_workflow_fail(
handle: *mut AgentDbHandle,
id: *const c_char,
error: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
let error_str = if error.is_null() {
None
} else {
CStr::from_ptr(error).to_str().ok()
};
match h.db.workflows().fail_workflow(id_str, error_str) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_workflow_get(
handle: *mut AgentDbHandle,
id: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return std::ptr::null_mut();
}
};
match h.db.workflows().get_workflow(id_str) {
Ok(w) => {
let steps: Vec<Value> = w
.steps
.iter()
.map(|s| {
serde_json::json!({
"id": s.id,
"step_index": s.step_index,
"name": s.name,
"status": s.status,
"input": s.input,
"output": s.output,
"error": s.error,
"started_at": s.started_at,
"completed_at": s.completed_at
})
})
.collect();
let json = serde_json::json!({
"id": w.id,
"name": w.name,
"status": w.status,
"input": w.input,
"output": w.output,
"metadata": w.metadata,
"created_at": w.created_at,
"updated_at": w.updated_at,
"steps": steps
});
CString::new(json.to_string())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_workflow_list(
handle: *mut AgentDbHandle,
status_filter: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let filter = if status_filter.is_null() {
None
} else {
CStr::from_ptr(status_filter).to_str().ok()
};
match h.db.workflows().list_workflows(filter) {
Ok(workflows) => {
let json: Vec<Value> = workflows
.iter()
.map(|w| {
serde_json::json!({
"id": w.id,
"name": w.name,
"status": w.status,
"created_at": w.created_at,
"updated_at": w.updated_at
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_trace_add(
handle: *mut AgentDbHandle,
session_id: *const c_char,
parent_id: *const c_char,
trace_type: *const c_char,
content: *const c_char,
metadata: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let sid = if session_id.is_null() {
None
} else {
CStr::from_ptr(session_id).to_str().ok()
};
let pid = if parent_id.is_null() {
None
} else {
CStr::from_ptr(parent_id).to_str().ok()
};
let tt = match CStr::from_ptr(trace_type).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid trace_type");
return std::ptr::null_mut();
}
};
let content_str = match CStr::from_ptr(content).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid content");
return std::ptr::null_mut();
}
};
let meta: Option<Value> = if metadata.is_null() {
None
} else {
CStr::from_ptr(metadata)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
match h.db.traces().add_trace(sid, pid, tt, content_str, meta) {
Ok(trace_id) => CString::new(trace_id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_trace_get_by_session(
handle: *mut AgentDbHandle,
session_id: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let sid = match CStr::from_ptr(session_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid session_id");
return std::ptr::null_mut();
}
};
match h.db.traces().get_traces(sid, None, None) {
Ok(traces) => {
let json: Vec<Value> = traces
.iter()
.map(|t| {
serde_json::json!({
"id": t.id,
"session_id": t.session_id,
"parent_id": t.parent_id,
"trace_type": t.trace_type,
"content": t.content,
"metadata": t.metadata,
"created_at": t.created_at
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_vector_delete(
handle: *mut AgentDbHandle,
collection: *const c_char,
id: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let col_name = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection name");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
let col = match h.db.vectors().get_collection(col_name) {
Ok(c) => c,
Err(e) => {
set_last_error(e.to_string());
return -1;
}
};
match col.delete(id_str) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_drop_collection(
handle: *mut AgentDbHandle,
collection: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let col_name = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection name");
return -1;
}
};
match h.db.vectors().drop_collection(col_name) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_reindex(
handle: *mut AgentDbHandle,
collection: *const c_char,
dim: usize,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let col_name = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection name");
return -1;
}
};
let col = match h.db.vectors().collection(col_name, dim) {
Ok(c) => c,
Err(e) => {
set_last_error(e.to_string());
return -1;
}
};
match col.reindex() {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_graph_get_node(
handle: *mut AgentDbHandle,
id: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return std::ptr::null_mut();
}
};
match h.db.memory().get_node(id_str) {
Ok(node) => {
let json = serde_json::json!({
"id": node.id,
"kind": node.kind,
"data": node.data,
"created_at": node.created_at,
"updated_at": node.updated_at
});
CString::new(json.to_string())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_graph_delete_node(
handle: *mut AgentDbHandle,
id: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let id_str = match CStr::from_ptr(id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid id");
return -1;
}
};
match h.db.memory().delete_node(id_str) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_graph_delete_edge(
handle: *mut AgentDbHandle,
src: *const c_char,
dst: *const c_char,
relation: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let src_str = match CStr::from_ptr(src).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid src");
return -1;
}
};
let dst_str = match CStr::from_ptr(dst).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid dst");
return -1;
}
};
let rel_str = match CStr::from_ptr(relation).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid relation");
return -1;
}
};
match h.db.memory().delete_edge(src_str, dst_str, rel_str) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_fts_delete(
handle: *mut AgentDbHandle,
collection: *const c_char,
vec_id: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let col = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection");
return -1;
}
};
let vid = match CStr::from_ptr(vec_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid vec_id");
return -1;
}
};
match h.db.fts().delete_text(col, vid) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_fts_optimize(
handle: *mut AgentDbHandle,
collection: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let col = match CStr::from_ptr(collection).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid collection");
return -1;
}
};
match h.db.fts().optimize(col) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_tool_register(
handle: *mut AgentDbHandle,
name: *const c_char,
description: *const c_char,
parameters_schema: *const c_char,
version: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let name_str = match CStr::from_ptr(name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid name");
return std::ptr::null_mut();
}
};
let desc = if description.is_null() {
None
} else {
CStr::from_ptr(description).to_str().ok()
};
let schema: Option<Value> = if parameters_schema.is_null() {
None
} else {
CStr::from_ptr(parameters_schema)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let ver = if version.is_null() {
None
} else {
CStr::from_ptr(version).to_str().ok()
};
match h.db.tools().register_tool(name_str, desc, schema, ver) {
Ok(id) => CString::new(id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_tool_list(handle: *mut AgentDbHandle) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
match h.db.tools().list_tools() {
Ok(tools) => {
let json: Vec<Value> = tools
.iter()
.map(|t| {
serde_json::json!({
"id": t.id,
"name": t.name,
"description": t.description,
"parameters_schema": t.parameters_schema,
"version": t.version,
"created_at": t.created_at,
"updated_at": t.updated_at
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_tool_log_call(
handle: *mut AgentDbHandle,
session_id: *const c_char,
tool_name: *const c_char,
arguments: *const c_char,
result: *const c_char,
error: *const c_char,
latency_ms: i64,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let sid = if session_id.is_null() {
None
} else {
CStr::from_ptr(session_id).to_str().ok()
};
let tn = match CStr::from_ptr(tool_name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid tool_name");
return std::ptr::null_mut();
}
};
let args: Option<Value> = if arguments.is_null() {
None
} else {
CStr::from_ptr(arguments)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let res: Option<Value> = if result.is_null() {
None
} else {
CStr::from_ptr(result)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let err = if error.is_null() {
None
} else {
CStr::from_ptr(error).to_str().ok()
};
let lat = if latency_ms < 0 {
None
} else {
Some(latency_ms)
};
match h.db.tools().log_tool_call(sid, tn, args, res, err, lat) {
Ok(id) => CString::new(id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_audit_log(
handle: *mut AgentDbHandle,
actor: *const c_char,
action: *const c_char,
table_name: *const c_char,
record_id: *const c_char,
old_value: *const c_char,
new_value: *const c_char,
reason: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let actor_opt = if actor.is_null() {
None
} else {
CStr::from_ptr(actor).to_str().ok()
};
let action_str = match CStr::from_ptr(action).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid action");
return std::ptr::null_mut();
}
};
let tbl = match CStr::from_ptr(table_name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid table_name");
return std::ptr::null_mut();
}
};
let rid = match CStr::from_ptr(record_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid record_id");
return std::ptr::null_mut();
}
};
let old: Option<Value> = if old_value.is_null() {
None
} else {
CStr::from_ptr(old_value)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let new: Option<Value> = if new_value.is_null() {
None
} else {
CStr::from_ptr(new_value)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
let reason_opt = if reason.is_null() {
None
} else {
CStr::from_ptr(reason).to_str().ok()
};
match h
.db
.audit()
.log(actor_opt, action_str, tbl, rid, old, new, reason_opt)
{
Ok(id) => CString::new(id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_audit_query_recent(
handle: *mut AgentDbHandle,
limit: usize,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let lim = if limit == 0 { None } else { Some(limit) };
match h.db.audit().query_recent(lim) {
Ok(entries) => {
let json: Vec<Value> = entries
.iter()
.map(|e| {
serde_json::json!({
"id": e.id,
"timestamp": e.timestamp,
"actor": e.actor,
"action": e.action,
"table_name": e.table_name,
"record_id": e.record_id,
"old_value": e.old_value,
"new_value": e.new_value,
"reason": e.reason
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_context_add(
handle: *mut AgentDbHandle,
session_id: *const c_char,
source_type: *const c_char,
source_id: *const c_char,
content_preview: *const c_char,
token_count: i64,
relevance_score: f64,
priority: i64,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let sid = match CStr::from_ptr(session_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid session_id");
return std::ptr::null_mut();
}
};
let st = match CStr::from_ptr(source_type).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid source_type");
return std::ptr::null_mut();
}
};
let si = match CStr::from_ptr(source_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid source_id");
return std::ptr::null_mut();
}
};
let preview = if content_preview.is_null() {
None
} else {
CStr::from_ptr(content_preview).to_str().ok()
};
match h
.db
.context()
.add_entry(sid, st, si, preview, token_count, relevance_score, priority)
{
Ok(id) => CString::new(id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_context_build_window(
handle: *mut AgentDbHandle,
session_id: *const c_char,
max_tokens: i64,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let sid = match CStr::from_ptr(session_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid session_id");
return std::ptr::null_mut();
}
};
match h.db.context().build_window(sid, max_tokens) {
Ok(entries) => {
let json: Vec<Value> = entries
.iter()
.map(|e| {
serde_json::json!({
"id": e.id,
"session_id": e.session_id,
"source_type": e.source_type,
"source_id": e.source_id,
"content_preview": e.content_preview,
"token_count": e.token_count,
"relevance_score": e.relevance_score,
"priority": e.priority,
"included_at": e.included_at
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_context_clear(
handle: *mut AgentDbHandle,
session_id: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let sid = match CStr::from_ptr(session_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid session_id");
return -1;
}
};
match h.db.context().clear_session(sid) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_prompt_create(
handle: *mut AgentDbHandle,
name: *const c_char,
template: *const c_char,
model_hint: *const c_char,
max_tokens: i64,
metadata: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let name_str = match CStr::from_ptr(name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid name");
return std::ptr::null_mut();
}
};
let tmpl_str = match CStr::from_ptr(template).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid template");
return std::ptr::null_mut();
}
};
let model = if model_hint.is_null() {
None
} else {
CStr::from_ptr(model_hint).to_str().ok()
};
let max_tok = if max_tokens < 0 {
None
} else {
Some(max_tokens)
};
let meta: Option<Value> = if metadata.is_null() {
None
} else {
CStr::from_ptr(metadata)
.to_str()
.ok()
.and_then(|s| serde_json::from_str(s).ok())
};
match h
.db
.prompts()
.create_template(name_str, tmpl_str, model, max_tok, meta)
{
Ok(id) => CString::new(id)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_prompt_render(
handle: *mut AgentDbHandle,
name: *const c_char,
vars_json: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let name_str = match CStr::from_ptr(name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid name");
return std::ptr::null_mut();
}
};
let vars: std::collections::HashMap<String, String> = if vars_json.is_null() {
std::collections::HashMap::new()
} else {
match CStr::from_ptr(vars_json).to_str() {
Ok(s) => serde_json::from_str::<serde_json::Map<String, Value>>(s)
.unwrap_or_default()
.into_iter()
.map(|(k, v)| {
(
k,
match v {
Value::String(s) => s,
other => other.to_string(),
},
)
})
.collect(),
Err(_) => {
set_last_error("invalid vars_json");
return std::ptr::null_mut();
}
}
};
match h.db.prompts().render(name_str, &vars) {
Ok(rendered) => CString::new(rendered)
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_label_tag(
handle: *mut AgentDbHandle,
table_name: *const c_char,
record_id: *const c_char,
label: *const c_char,
tagged_by: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let tbl = match CStr::from_ptr(table_name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid table_name");
return -1;
}
};
let rid = match CStr::from_ptr(record_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid record_id");
return -1;
}
};
let lbl = match CStr::from_ptr(label).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid label");
return -1;
}
};
let by = if tagged_by.is_null() {
None
} else {
CStr::from_ptr(tagged_by).to_str().ok()
};
match h.db.labels().tag(tbl, rid, lbl, by) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_label_untag(
handle: *mut AgentDbHandle,
table_name: *const c_char,
record_id: *const c_char,
label: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let tbl = match CStr::from_ptr(table_name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid table_name");
return -1;
}
};
let rid = match CStr::from_ptr(record_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid record_id");
return -1;
}
};
let lbl = match CStr::from_ptr(label).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid label");
return -1;
}
};
match h.db.labels().untag(tbl, rid, lbl) {
Ok(()) => 0,
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_label_get(
handle: *mut AgentDbHandle,
table_name: *const c_char,
record_id: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let tbl = match CStr::from_ptr(table_name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid table_name");
return std::ptr::null_mut();
}
};
let rid = match CStr::from_ptr(record_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid record_id");
return std::ptr::null_mut();
}
};
match h.db.labels().get_labels(tbl, rid) {
Ok(labels) => {
let json: Vec<Value> = labels
.iter()
.map(|l| {
serde_json::json!({
"table_name": l.table_name,
"record_id": l.record_id,
"label": l.label,
"tagged_by": l.tagged_by,
"tagged_at": l.tagged_at
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_label_has(
handle: *mut AgentDbHandle,
table_name: *const c_char,
record_id: *const c_char,
label: *const c_char,
) -> i32 {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return -1;
}
};
let tbl = match CStr::from_ptr(table_name).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid table_name");
return -1;
}
};
let rid = match CStr::from_ptr(record_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid record_id");
return -1;
}
};
let lbl = match CStr::from_ptr(label).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid label");
return -1;
}
};
match h.db.labels().has_label(tbl, rid, lbl) {
Ok(has) => {
if has {
1
} else {
0
}
}
Err(e) => {
set_last_error(e.to_string());
-1
}
}
}
#[no_mangle]
pub unsafe extern "C" fn agentdb_trace_get_tree(
handle: *mut AgentDbHandle,
root_id: *const c_char,
) -> *mut c_char {
clear_last_error();
let h = match handle.as_ref() {
Some(h) => h,
None => {
set_last_error("null handle");
return std::ptr::null_mut();
}
};
let rid = match CStr::from_ptr(root_id).to_str() {
Ok(s) => s,
Err(_) => {
set_last_error("invalid root_id");
return std::ptr::null_mut();
}
};
match h.db.traces().get_trace_tree(rid) {
Ok(traces) => {
let json: Vec<Value> = traces
.iter()
.map(|t| {
serde_json::json!({
"id": t.id,
"session_id": t.session_id,
"parent_id": t.parent_id,
"trace_type": t.trace_type,
"content": t.content,
"metadata": t.metadata,
"created_at": t.created_at
})
})
.collect();
let s = Value::Array(json).to_string();
CString::new(s)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
Err(e) => {
set_last_error(e.to_string());
std::ptr::null_mut()
}
}
}