rho-coding-agent 1.19.2

A lightweight agent harness inspired by Pi
Documentation
use rho_sdk::{
    tool::{
        OperationKind, PreparedToolInvocation, Tool, ToolError, ToolErrorKind, ToolInvocation,
        ToolMetadata, ToolOutput, ToolPreparationContext, ToolPrepareFuture, ToolSecurity,
    },
    CapabilityKind, CapabilityRequest, CapabilitySource, NetworkTarget,
};
use rho_tools::tool::{Tool as LegacyTool, ToolContext as LegacyToolContext};

use super::WebSearch;

const WEB_SEARCH_TOOL: &str = "web_search";

pub(in crate::tools) struct SdkWebSearch {
    inner: WebSearch,
    max_output_bytes: usize,
}

impl SdkWebSearch {
    pub(in crate::tools) fn new(inner: WebSearch, max_output_bytes: usize) -> Self {
        Self {
            inner,
            max_output_bytes,
        }
    }

    async fn execute(
        &self,
        arguments: serde_json::Value,
        context: &rho_sdk::tool::AuthorizedToolContext,
    ) -> Result<ToolOutput, ToolError> {
        let cwd = context
            .workspace_root()
            .map(std::path::Path::to_path_buf)
            .unwrap_or_default();
        let execution = self.inner.call(
            arguments,
            LegacyToolContext {
                cwd,
                max_output_bytes: self.max_output_bytes,
            },
            String::new(),
        );
        let result = tokio::select! {
            result = execution => result,
            () = context.cancellation().cancelled() => return Err(ToolError::cancelled()),
        }
        .map_err(map_legacy_error)?;
        if !result.ok {
            return Err(ToolError::new(ToolErrorKind::Execution, result.content));
        }
        Ok(ToolOutput::text(result.content).metadata(metadata()))
    }
}

impl Tool for SdkWebSearch {
    fn spec(&self) -> rho_sdk::model::ToolSpec {
        self.inner.spec()
    }

    fn security(&self) -> ToolSecurity {
        ToolSecurity::built_in([CapabilityKind::Network])
    }

    fn prepare<'a>(
        &'a self,
        invocation: ToolInvocation,
        _context: ToolPreparationContext,
    ) -> ToolPrepareFuture<'a> {
        let arguments = invocation.into_arguments();
        Box::pin(async move {
            let capability = CapabilityRequest::network(
                NetworkTarget::ToolManaged,
                CapabilitySource::built_in_tool(WEB_SEARCH_TOOL),
            );
            Ok(PreparedToolInvocation::resource_aware(
                [],
                [capability],
                metadata(),
                move |context| Box::pin(async move { self.execute(arguments, &context).await }),
            ))
        })
    }
}

fn metadata() -> ToolMetadata {
    ToolMetadata::new().operation(OperationKind::Network)
}

fn map_legacy_error(error: rho_tools::tool::ToolError) -> ToolError {
    match error {
        rho_tools::tool::ToolError::InvalidArguments(error) => {
            ToolError::new(ToolErrorKind::InvalidArguments, error.to_string())
        }
        rho_tools::tool::ToolError::Cancelled => ToolError::cancelled(),
        error => ToolError::new(ToolErrorKind::Execution, error.to_string()),
    }
}