agent-file-tools 0.43.1

Agent File Tools — tree-sitter powered code analysis for AI agents
Documentation
use serde_json::{json, Value};
use std::sync::OnceLock;

use crate::context::AppContext;
use crate::protocol::{RawRequest, Response};
use crate::run_tool_call::{run_tool_call, DispatchFn, ToolCallContext, ToolCallOutcome};

type StandaloneDispatch = fn(RawRequest, &AppContext) -> Response;

static STANDALONE_DISPATCH: OnceLock<StandaloneDispatch> = OnceLock::new();

pub fn register_dispatch(dispatch: StandaloneDispatch) {
    let _ = STANDALONE_DISPATCH.set(dispatch);
}

pub fn handle(req: &RawRequest, ctx: &AppContext) -> Response {
    let Some(dispatch) = STANDALONE_DISPATCH.get().copied() else {
        return Response::error(
            &req.id,
            "internal_error",
            "tool_call: standalone dispatcher is not registered",
        );
    };
    handle_with_dispatch(req, ctx, &dispatch)
}

fn handle_with_dispatch(req: &RawRequest, ctx: &AppContext, dispatch: &DispatchFn<'_>) -> Response {
    let Some(name) = req
        .params
        .get("name")
        .and_then(Value::as_str)
        .filter(|name| !name.is_empty())
    else {
        return Response::error(
            &req.id,
            "invalid_request",
            "tool_call: missing or invalid required string field 'name'",
        );
    };

    if name == "tool_call" {
        return Response::error(
            &req.id,
            "invalid_request",
            "tool_call: recursive tool_call requests are not supported",
        );
    }

    let arguments = req
        .params
        .get("arguments")
        .cloned()
        .unwrap_or_else(|| json!({}));
    let preview = req
        .params
        .get("preview")
        .and_then(Value::as_bool)
        .unwrap_or(false);
    let config = ctx.config();
    let project_root = config
        .project_root
        .clone()
        .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
    let tool_ctx = ToolCallContext {
        project_root,
        session_id: Some(req.session().to_string()),
        request_id: req.id.clone(),
        diagnostics_on_edit: config.diagnostics_on_edit,
        preview,
    };

    match run_tool_call(name, &arguments, &tool_ctx, ctx, dispatch) {
        ToolCallOutcome::Unary(result) => response_with_text(result.response, result.text),
    }
}

fn response_with_text(mut response: Response, text: String) -> Response {
    if let Some(data) = response.data.as_object_mut() {
        data.insert("text".to_string(), Value::String(text));
    } else {
        response.data = json!({ "text": text });
    }
    response
}