mentra 0.5.0

An agent runtime for tool-using LLM applications
Documentation
use crate::{
    ContentBlock,
    agent::Agent,
    error::RuntimeError,
    team::{TeamProtocolStatus, TeamRequestDirection, TeamRequestFilter},
    tool::{ParallelToolContext, ToolCall, ToolResult},
};

use super::schema::{
    TeamBroadcastInput, TeamListRequestsInput, TeamRequestInput, TeamRespondInput, TeamSendInput,
    TeamSpawnInput,
};

pub(super) async fn execute_team_spawn(agent: &mut Agent, call: ToolCall) -> ContentBlock {
    let input = match serde_json::from_value::<TeamSpawnInput>(call.input) {
        Ok(input) => input,
        Err(error) => {
            return ContentBlock::ToolResult {
                tool_use_id: call.id,
                content: format!("Invalid team_spawn input: {error}").into(),
                is_error: true,
            };
        }
    };

    match agent
        .spawn_teammate(input.name, input.role, input.prompt)
        .await
    {
        Ok(teammate) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!(
                "Spawned persistent teammate '{}' (role: {}, status: {})",
                teammate.name, teammate.role, teammate.status
            )
            .into(),
            is_error: false,
        },
        Err(error) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!("Failed to spawn teammate: {error}").into(),
            is_error: true,
        },
    }
}

pub(super) fn execute_team_send(agent: &mut Agent, call: ToolCall) -> ContentBlock {
    let input = match serde_json::from_value::<TeamSendInput>(call.input) {
        Ok(input) => input,
        Err(error) => {
            return ContentBlock::ToolResult {
                tool_use_id: call.id,
                content: format!("Invalid team_send input: {error}").into(),
                is_error: true,
            };
        }
    };

    match agent.send_team_message(&input.to, input.content) {
        Ok(dispatch) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!("Sent message to '{}'", dispatch.teammate).into(),
            is_error: false,
        },
        Err(error) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!("Failed to send team message: {error}").into(),
            is_error: true,
        },
    }
}

pub(super) fn execute_team_read_inbox(agent: &mut Agent, call: ToolCall) -> ContentBlock {
    match agent.read_team_inbox().and_then(|messages| {
        serde_json::to_string_pretty(&messages).map_err(RuntimeError::FailedToSerializeTeam)
    }) {
        Ok(content) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: content.into(),
            is_error: false,
        },
        Err(error) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!("Failed to read team inbox: {error}").into(),
            is_error: true,
        },
    }
}

pub(super) fn execute_team_broadcast(agent: &mut Agent, call: ToolCall) -> ContentBlock {
    let input = match serde_json::from_value::<TeamBroadcastInput>(call.input) {
        Ok(input) => input,
        Err(error) => {
            return ContentBlock::ToolResult {
                tool_use_id: call.id,
                content: format!("Invalid broadcast input: {error}").into(),
                is_error: true,
            };
        }
    };

    match agent.broadcast_team_message(input.content) {
        Ok(dispatches) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!(
                "Broadcast message sent to {} recipient(s): {}",
                dispatches.len(),
                dispatches
                    .into_iter()
                    .map(|dispatch| dispatch.teammate)
                    .collect::<Vec<_>>()
                    .join(", ")
            )
            .into(),
            is_error: false,
        },
        Err(error) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!("Failed to broadcast team message: {error}").into(),
            is_error: true,
        },
    }
}

pub(super) fn execute_team_request(agent: &mut Agent, call: ToolCall) -> ContentBlock {
    let input = match serde_json::from_value::<TeamRequestInput>(call.input) {
        Ok(input) => input,
        Err(error) => {
            return ContentBlock::ToolResult {
                tool_use_id: call.id,
                content: format!("Invalid team_request input: {error}").into(),
                is_error: true,
            };
        }
    };

    match agent.request_team_protocol(&input.to, input.protocol, input.content) {
        Ok(request) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!(
                "Created team request '{}' for '{}' using protocol '{}'",
                request.request_id, request.to, request.protocol
            )
            .into(),
            is_error: false,
        },
        Err(error) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!("Failed to create team request: {error}").into(),
            is_error: true,
        },
    }
}

pub(super) fn execute_team_respond(agent: &mut Agent, call: ToolCall) -> ContentBlock {
    let input = match serde_json::from_value::<TeamRespondInput>(call.input) {
        Ok(input) => input,
        Err(error) => {
            return ContentBlock::ToolResult {
                tool_use_id: call.id,
                content: format!("Invalid team_respond input: {error}").into(),
                is_error: true,
            };
        }
    };

    match agent.respond_team_protocol(&input.request_id, input.approve, input.reason) {
        Ok(request) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!(
                "{} team request '{}' ({})",
                if input.approve {
                    "Approved"
                } else {
                    "Rejected"
                },
                request.request_id,
                request.protocol
            )
            .into(),
            is_error: false,
        },
        Err(error) => ContentBlock::ToolResult {
            tool_use_id: call.id,
            content: format!("Failed to respond to team request: {error}").into(),
            is_error: true,
        },
    }
}

pub(super) fn execute_team_list_requests_parallel(
    ctx: ParallelToolContext,
    input: serde_json::Value,
) -> ToolResult {
    let filter = parse_team_request_filter(input)?;
    let config = ctx.runtime.agent_config(&ctx.agent_id)?;
    let requests = ctx
        .runtime
        .list_team_requests(&config.team_dir, &config.name, filter)
        .map_err(|error| format!("Failed to list team requests: {error}"))?;

    serde_json::to_string_pretty(&requests)
        .map_err(|error| format!("Failed to serialize team requests: {error}"))
}

fn parse_team_request_filter(input: serde_json::Value) -> Result<TeamRequestFilter, String> {
    let input = serde_json::from_value::<TeamListRequestsInput>(input)
        .map_err(|error| format!("Invalid team_list_requests input: {error}"))?;

    let status = match input.status.as_deref() {
        Some("pending") => Some(TeamProtocolStatus::Pending),
        Some("approved") => Some(TeamProtocolStatus::Approved),
        Some("rejected") => Some(TeamProtocolStatus::Rejected),
        Some(value) => return Err(format!("Invalid team_list_requests status '{value}'")),
        None => None,
    };

    let direction = match input.direction.as_deref() {
        Some("inbound") => TeamRequestDirection::Inbound,
        Some("outbound") => TeamRequestDirection::Outbound,
        Some("any") | None => TeamRequestDirection::Any,
        Some(value) => return Err(format!("Invalid team_list_requests direction '{value}'")),
    };

    Ok(TeamRequestFilter {
        status,
        protocol: input.protocol,
        counterparty: input.counterparty,
        direction,
    })
}