use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::path::PathBuf;
pub mod fs;
pub mod hint_accumulator;
pub mod server;
pub mod shared_utils;
struct WorkDir {
session: PathBuf,
current: PathBuf,
}
thread_local! {
static WORKDIR: std::cell::RefCell<Option<WorkDir>> = const { std::cell::RefCell::new(None) };
}
pub fn set_session_working_directory(path: PathBuf) {
WORKDIR.with(|w| {
*w.borrow_mut() = Some(WorkDir {
session: path.clone(),
current: path,
});
});
}
pub fn set_thread_working_directory(path: PathBuf) {
WORKDIR.with(|w| {
let mut w = w.borrow_mut();
if let Some(ref mut wd) = *w {
wd.current = path;
}
});
}
pub fn get_thread_working_directory() -> PathBuf {
WORKDIR.with(|w| {
w.borrow()
.as_ref()
.map(|wd| wd.current.clone())
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default())
})
}
pub fn get_thread_original_working_directory() -> PathBuf {
WORKDIR.with(|w| {
w.borrow()
.as_ref()
.map(|wd| wd.session.clone())
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default())
})
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolCall {
pub tool_name: String,
pub parameters: Value,
#[serde(default)]
pub tool_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolResult {
pub tool_name: String,
pub result: Value,
#[serde(default)]
pub tool_id: String,
}
impl McpToolResult {
pub fn success(tool_name: String, tool_id: String, content: String) -> Self {
Self {
tool_name,
tool_id,
result: json!({
"content": [
{
"type": "text",
"text": content
}
],
"isError": false
}),
}
}
pub fn success_with_metadata(
tool_name: String,
tool_id: String,
content: String,
metadata: serde_json::Value,
) -> Self {
Self {
tool_name,
tool_id,
result: json!({
"content": [
{
"type": "text",
"text": content
}
],
"isError": false,
"metadata": metadata
}),
}
}
pub fn error(tool_name: String, tool_id: String, error_message: String) -> Self {
Self {
tool_name,
tool_id,
result: json!({
"content": [
{
"type": "text",
"text": error_message
}
],
"isError": true
}),
}
}
pub fn is_error(&self) -> bool {
self.result
.get("isError")
.and_then(|v| v.as_bool())
.unwrap_or(false)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpFunction {
pub name: String,
pub description: String,
pub parameters: Value,
}
pub fn extract_mcp_content(result: &Value) -> String {
if let Some(content_array) = result.get("content") {
if let Some(content_items) = content_array.as_array() {
return content_items
.iter()
.filter_map(|item| {
if item.get("type").and_then(|t| t.as_str()) == Some("text") {
item.get("text").and_then(|t| t.as_str())
} else {
None
}
})
.collect::<Vec<_>>()
.join("\n");
}
}
serde_json::to_string_pretty(result).unwrap_or_default()
}
pub fn ensure_tool_call_ids(calls: &mut [McpToolCall]) {
for call in calls.iter_mut() {
if call.tool_id.is_empty() {
call.tool_id = format!("tool_{}", uuid::Uuid::new_v4().simple());
}
}
}