systemprompt-agent 0.63.0

Agent-to-Agent (A2A) protocol for systemprompt.io AI governance: streaming, JSON-RPC models, task lifecycle, .well-known discovery, and governed agent orchestration.
Documentation
//! `ToolExecutorTrait` implementation executing tools in context.
//!
//! Copyright (c) systemprompt.io — Business Source License 1.1.
//! See <https://systemprompt.io> for licensing details.

use crate::services::shared::{AgentServiceError, Result};
use async_trait::async_trait;
use serde_json::Value;
use systemprompt_identifiers::{AiToolCallId, McpToolName};
use systemprompt_models::{McpTool, RequestContext, ToolCall};

use super::ExecutionContext;
use super::plan_executor::{ToolExecutorTrait, ToolOutcome};

#[derive(Debug)]
pub struct ContextToolExecutor {
    pub context: ExecutionContext,
}

#[async_trait]
impl ToolExecutorTrait for ContextToolExecutor {
    // JSON: MCP-protocol boundary — schema-less tool arguments mandated by the
    // spec.
    async fn execute_tool(
        &self,
        tool_name: &McpToolName,
        arguments: Value,
        tools: &[McpTool],
        ctx: &RequestContext,
    ) -> Result<ToolOutcome> {
        let tool_call = ToolCall {
            ai_tool_call_id: AiToolCallId::new(format!("call_{}", tool_name)),
            name: tool_name.to_string(),
            arguments,
        };

        let (_, results) = self
            .context
            .ai_service
            .execute_tools(
                vec![tool_call],
                tools,
                ctx,
                Some(&self.context.agent_runtime.tool_model_overrides),
            )
            .await;

        let result =
            results
                .into_iter()
                .next()
                .ok_or_else(|| AgentServiceError::ToolReturnedNoResult {
                    tool_name: tool_name.clone(),
                })?;

        if result.is_error.unwrap_or(false) {
            let error_msg = result
                .content
                .into_iter()
                .next()
                .and_then(|c| {
                    if let rmcp::model::ContentBlock::Text(text_content) = c {
                        Some(text_content.text)
                    } else {
                        None
                    }
                })
                .unwrap_or_else(|| "Unknown error".to_owned());
            return Err(AgentServiceError::ToolFailed {
                tool_name: tool_name.clone(),
                message: error_msg,
            });
        }

        let output = result.structured_content.ok_or_else(|| {
            AgentServiceError::ToolReturnedNoStructuredContent {
                tool_name: tool_name.clone(),
            }
        })?;
        Ok(ToolOutcome {
            output,
            meta: result.meta.as_ref().and_then(|m| {
                serde_json::to_value(m)
                    .inspect_err(|e| tracing::warn!(error = %e, "Failed to serialize tool meta"))
                    .ok()
            }),
        })
    }
}