car-server-core 0.25.0

Transport-neutral library for the CAR daemon JSON-RPC dispatcher (used by car-server and tokhn-daemon)
//! Agent-callable engine tools for the Parslee platform.
//!
//! [`ParsleeToolExecutor`] adapts the daemon's `parslee.*` handlers to the
//! [`car_engine::ToolExecutor`] interface so in-daemon declarative agents can
//! call them by name (subject to the agent's strict tool allowlist). It is
//! wired in as a *delegate* on the agent's [`crate::coder::shell_tool::WorktreeExecutor`]:
//! the worktree executor handles file/shell tools and routes unknown,
//! delegate-owned names (the two below) here.
//!
//! The tools carry the same auth + entitlement gating as the JSON-RPC and FFI
//! surfaces — `parslee_m365_generate_document` still refuses unless the account
//! is signed in, has an active org, and holds the `aie` entitlement.

use async_trait::async_trait;
use car_engine::ToolExecutor;
use serde_json::{json, Value};

/// Engine tool name: read-only capability/entitlement discovery.
pub const TOOL_CAPABILITIES: &str = "parslee_capabilities";
/// Engine tool name: m365 Word-document generation (gated on `aie`).
pub const TOOL_GENERATE_DOCUMENT: &str = "parslee_m365_generate_document";

/// In-daemon executor for the Parslee platform tools. Stateless — the Parslee
/// bearer is resolved per call from the signed-in session.
pub struct ParsleeToolExecutor;

impl ParsleeToolExecutor {
    /// Tool definitions in the `{name, description, parameters}` shape the
    /// agent loop advertises to the model.
    pub fn tool_defs() -> Vec<Value> {
        vec![
            json!({
                "name": TOOL_CAPABILITIES,
                "description": "Discover what the signed-in Parslee account can do: \
                    identity, enabled product entitlements (studio, aie, odi, …), and \
                    Studio reachability. Read-only. Call this first to learn which \
                    products are available before using a product tool.",
                "parameters": { "type": "object", "properties": {} }
            }),
            json!({
                "name": TOOL_GENERATE_DOCUMENT,
                "description": "Generate a Word document from a natural-language brief and \
                    save it to the user's connected drive (OneDrive/Google Drive). Requires \
                    the 'aie' entitlement. Returns the saved file's id and web URL.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "content_brief": {
                            "type": "string",
                            "description": "What the document should contain (20–2000 characters)."
                        },
                        "output_file_path": {
                            "type": "string",
                            "description": "Path in the user's drive, e.g. \"Generated/report.docx\"."
                        },
                        "document_type": {
                            "type": "string",
                            "description": "Report|Proposal|Memo|Letter|Contract|ExecutiveSummary|MeetingMinutes|ProjectPlan (default Report)."
                        },
                        "title": { "type": "string", "description": "Optional title override." },
                        "author": { "type": "string", "description": "Optional author name." }
                    },
                    "required": ["content_brief", "output_file_path"]
                }
            }),
        ]
    }

    /// The engine tool names this executor owns. Advertised so the coder→agent
    /// build loop can offer them to generated agents' allowlists.
    pub fn tool_names() -> Vec<String> {
        vec![
            TOOL_CAPABILITIES.to_string(),
            TOOL_GENERATE_DOCUMENT.to_string(),
        ]
    }
}

#[async_trait]
impl ToolExecutor for ParsleeToolExecutor {
    async fn execute(&self, tool: &str, params: &Value) -> Result<Value, String> {
        match tool {
            TOOL_CAPABILITIES => crate::parslee_capabilities::discover().await,
            TOOL_GENERATE_DOCUMENT => crate::parslee_m365::generate_document(params).await,
            other => Err(format!("unknown Parslee tool: {other}")),
        }
    }
}