agents-kit 0.0.2

Rust agent control Kit for Codex, OpenCode, and provider-backed Custom runtimes
Documentation
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};

use crate::backends::opencode::adapters::OpenCodeSelectedModel;
use crate::backends::opencode::protocol::http::OpenCodeHttp;
use crate::backends::opencode::protocol::requests::OpenCodeRouting;
use crate::backends::opencode::protocol::responses::{
    OpenCodeToolDescription, OpenCodeToolIds, OpenCodeToolInventory,
};
use crate::{KitError, ToolInfo};

const MAX_NATIVE_TOOLS: usize = 65_536;

#[derive(Clone)]
pub(crate) struct OpenCodeTools {
    runtime_id: Arc<str>,
    revision: Arc<AtomicU64>,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct OpenCodeCatalogGeneration {
    token: String,
}

pub(crate) struct OpenCodeToolCatalog {
    generation: OpenCodeCatalogGeneration,
    tools: Vec<ToolInfo>,
}

impl OpenCodeTools {
    pub(crate) fn new() -> Self {
        Self {
            runtime_id: Arc::from(uuid::Uuid::new_v4().simple().to_string()),
            revision: Arc::new(AtomicU64::new(0)),
        }
    }

    pub(crate) fn generation(&self) -> OpenCodeCatalogGeneration {
        OpenCodeCatalogGeneration::new(&self.runtime_id, self.revision.load(Ordering::Acquire))
    }

    pub(crate) fn invalidate(&self) -> Result<(), KitError> {
        self.revision
            .try_update(Ordering::AcqRel, Ordering::Acquire, |revision| {
                revision.checked_add(1)
            })
            .map(|_| ())
            .map_err(|_| KitError::invalid_state("tool catalog generation exhausted"))
    }

    pub(crate) async fn discover(
        &self,
        http: &OpenCodeHttp,
        routing: &OpenCodeRouting,
        selected_model: Option<&OpenCodeSelectedModel>,
    ) -> Result<OpenCodeToolCatalog, KitError> {
        let selected_model = selected_model.ok_or_else(|| {
            KitError::not_yet_implemented(
                "tool inventory requires a concrete selected workspace model",
            )
        })?;
        let generation = self.generation();

        if http.mcp_statuses(routing).await?.has_connected() {
            return Err(KitError::not_yet_implemented(
                "connected native tool sources lack a complete selected inventory route",
            ));
        }
        let first_ids = self.validate_ids(http.tool_ids(routing).await?)?;
        let descriptions = http.tools(routing, selected_model.native()).await?;
        let second_ids = self.validate_ids(http.tool_ids(routing).await?)?;
        if first_ids != second_ids {
            return Err(KitError::conflict(
                "tool inventory changed during discovery",
            ));
        }
        if http.mcp_statuses(routing).await?.has_connected() {
            return Err(KitError::not_yet_implemented(
                "connected native tool sources lack a complete selected inventory route",
            ));
        }
        if self.generation() != generation {
            return Err(KitError::conflict(
                "tool catalog was invalidated during discovery",
            ));
        }

        let tools = self.project(first_ids, descriptions)?;
        Ok(OpenCodeToolCatalog { generation, tools })
    }

    fn validate_ids(&self, native: OpenCodeToolIds) -> Result<Vec<String>, KitError> {
        if native.ids().len() > MAX_NATIVE_TOOLS {
            return Err(KitError::integration(
                "tool inventory exceeds its configured bound",
            ));
        }
        let mut ids = native.ids().to_vec();
        if ids.iter().any(|id| id.is_empty() || id.trim() != id) {
            return Err(KitError::protocol(
                "tool inventory contains a malformed name",
            ));
        }
        ids.sort();
        if ids.windows(2).any(|pair| pair[0] == pair[1]) {
            return Err(KitError::invalid_state(
                "tool inventory contains duplicate names",
            ));
        }
        Ok(ids)
    }

    fn project(
        &self,
        ids: Vec<String>,
        native: OpenCodeToolInventory,
    ) -> Result<Vec<ToolInfo>, KitError> {
        if native.descriptions().len() > MAX_NATIVE_TOOLS {
            return Err(KitError::integration(
                "tool descriptions exceed their configured bound",
            ));
        }
        let mut descriptions = BTreeMap::new();
        for description in native.descriptions() {
            let name = description.id();
            if name.is_empty() || name.trim() != name {
                return Err(KitError::protocol(
                    "tool description contains a malformed name",
                ));
            }
            if descriptions.insert(name.to_owned(), description).is_some() {
                return Err(KitError::invalid_state(
                    "tool descriptions contain duplicate names",
                ));
            }
        }
        if descriptions.is_empty() {
            return Err(KitError::invalid_state(
                "selected tool descriptions are unexpectedly empty",
            ));
        }
        for name in descriptions.keys() {
            if ids.binary_search(name).is_err() {
                return Err(KitError::invalid_state(
                    "tool description is absent from the candidate inventory",
                ));
            }
        }

        descriptions
            .into_iter()
            .map(|(id, description)| self.project_tool(id, description))
            .collect()
    }

    fn project_tool(
        &self,
        id: String,
        native: &OpenCodeToolDescription,
    ) -> Result<ToolInfo, KitError> {
        let parameters = native.parameters();
        if jsonschema::draft202012::meta::validate(parameters).is_err() {
            return Err(KitError::protocol("tool input schema is malformed"));
        }
        let description = native.description().trim();
        let description = if description.is_empty() {
            if native.description().is_empty() {
                None
            } else {
                return Err(KitError::protocol("tool description is malformed"));
            }
        } else {
            Some(description.to_owned())
        };
        ToolInfo::new(id, description, Some(parameters.clone()))
            .map_err(|_| KitError::protocol("tool projection is malformed"))
    }
}

impl OpenCodeCatalogGeneration {
    fn new(runtime_id: &str, revision: u64) -> Self {
        Self {
            token: format!("{runtime_id}:{revision}"),
        }
    }

    pub(crate) fn as_str(&self) -> &str {
        &self.token
    }
}

impl OpenCodeToolCatalog {
    pub(crate) fn generation(&self) -> &OpenCodeCatalogGeneration {
        &self.generation
    }

    pub(crate) fn into_tools(self) -> Vec<ToolInfo> {
        self.tools
    }
}