everruns-builtins 0.18.8

Portable, backend-neutral built-in capabilities for Everruns
Documentation
// Parallel tool calls capability
//
// Controls whether the agent asks the provider to emit multiple tool calls per
// turn (and whether the local tool scheduler runs a batch concurrently):
//
// - `prefer`: explicitly request parallel tool calls. On providers that expose
//   a wire control (OpenAI/Anthropic families) this is sent on the request; the
//   local scheduler keeps its class-aware concurrent default. Lets the model
//   batch independent reads/searches instead of relying on each provider's
//   undocumented default.
// - `avoid`: ask the provider to emit at most one tool call per turn AND force
//   the local tool scheduler to serialize the batch. The local serialization
//   applies to every driver, so `avoid` is honored even on providers without a
//   wire control (Gemini/Bedrock).
// - `none`: no preference — omit the field and keep the provider default and the
//   scheduler's concurrent schedule. Same effect as not enabling the capability;
//   useful to neutralize an inherited preference from a parent harness.
//
// The resolved preference threads through `RuntimeAgent.parallel_tool_calls`
// into both the LLM request (provider-gated, see `ChatDriver::
// supports_parallel_tool_calls`) and `ActInput.parallel_tool_calls` (the local
// scheduler). An explicit `parallel_tool_calls` field on harness/agent/session
// is a lower-level escape hatch and takes precedence over this capability.

use super::{Capability, CapabilityLocalization, SystemPromptContext};
use async_trait::async_trait;

/// Capability ID for the request-level parallel tool calls preference.
pub const PARALLEL_TOOL_CALLS_CAPABILITY_ID: &str = "parallel_tool_calls";

/// Resolved preference mode for the `parallel_tool_calls` capability.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParallelToolCallsMode {
    /// Request parallel tool calls (provider + concurrent scheduler).
    Prefer,
    /// Disable parallel tool calls (provider hint + serialized scheduler).
    Avoid,
    /// No preference — provider default and concurrent scheduler.
    None,
}

impl ParallelToolCallsMode {
    /// Parse a config string into a mode. Unknown values yield `None`.
    pub fn parse(value: &str) -> Option<Self> {
        match value {
            "prefer" => Some(Self::Prefer),
            "avoid" => Some(Self::Avoid),
            "none" => Some(Self::None),
            _ => None,
        }
    }

    /// Map the mode onto the request-level `parallel_tool_calls` preference
    /// carried by `RuntimeAgent`/`LlmCallConfig`/`ActInput`.
    ///
    /// `None` mode resolves to `None` (omit, provider default).
    pub fn to_preference(self) -> Option<bool> {
        match self {
            Self::Prefer => Some(true),
            Self::Avoid => Some(false),
            Self::None => None,
        }
    }
}

/// Resolve the `parallel_tool_calls` capability config into a request-level
/// preference.
///
/// - Capability present with no explicit `mode` (empty/`null` config) → `prefer`.
/// - Valid `mode` → that mode (`none` resolves to no preference).
/// - Malformed config (not an object) or an invalid/non-string `mode` → `None`,
///   so a bad runtime config neutralizes the capability rather than silently
///   enabling parallel tool calls. (`validate_config` already rejects these on
///   the write path; this is the defensive runtime fallback.)
pub fn parallel_tool_calls_from_config(config: &serde_json::Value) -> Option<bool> {
    if config.is_null() {
        return ParallelToolCallsMode::Prefer.to_preference();
    }
    let object = config.as_object()?;
    match object.get("mode") {
        None => ParallelToolCallsMode::Prefer.to_preference(),
        Some(serde_json::Value::String(mode)) => {
            ParallelToolCallsMode::parse(mode).and_then(ParallelToolCallsMode::to_preference)
        }
        Some(_) => None,
    }
}

/// Parallel tool calls capability.
///
/// Adds no tools or prompt text; it only configures the outbound LLM request
/// and the local tool scheduler.
pub struct ParallelToolCallsCapability;

#[async_trait]
impl Capability for ParallelToolCallsCapability {
    fn id(&self) -> &str {
        PARALLEL_TOOL_CALLS_CAPABILITY_ID
    }

    fn name(&self) -> &str {
        "Parallel Tool Calls"
    }

    fn description(&self) -> &str {
        "Controls whether the agent requests multiple tool calls per turn and \
         runs them concurrently: prefer (request parallel), avoid (one at a \
         time, serialized), or none (provider default)."
    }

    fn category(&self) -> Option<&str> {
        Some("Optimization")
    }

    fn parallel_tool_calls_preference(&self, config: &serde_json::Value) -> Option<bool> {
        parallel_tool_calls_from_config(config)
    }

    fn config_schema(&self) -> Option<serde_json::Value> {
        Some(serde_json::json!({
            "type": "object",
            "properties": {
                "mode": {
                    "type": "string",
                    "title": "Parallel tool calls",
                    "description": "prefer: request parallel tool calls (default); avoid: one tool call per turn, serialized locally; none: provider default.",
                    "enum": ["prefer", "avoid", "none"],
                    "default": "prefer"
                }
            }
        }))
    }

    fn validate_config(&self, config: &serde_json::Value) -> Result<(), String> {
        if config.is_null() {
            return Ok(());
        }
        if !config.is_object() {
            return Err("parallel_tool_calls config must be an object".to_string());
        }
        match config.get("mode") {
            None => Ok(()),
            Some(serde_json::Value::String(mode))
                if ParallelToolCallsMode::parse(mode).is_some() =>
            {
                Ok(())
            }
            Some(value) => Err(format!(
                "mode must be one of \"prefer\", \"avoid\", \"none\", got {value}"
            )),
        }
    }

    fn localizations(&self) -> Vec<CapabilityLocalization> {
        vec![
            CapabilityLocalization {
                locale: "en",
                name: None,
                description: None,
                config_description: Some(
                    "Chooses whether the agent batches independent tool calls or runs them one at a time.",
                ),
                config_overlay: None,
            },
            CapabilityLocalization {
                locale: "uk",
                name: Some("Паралельні виклики інструментів"),
                description: Some(
                    "Визначає, чи запитує агент кілька викликів інструментів за хід і чи виконує їх одночасно: prefer (запитувати паралельні), avoid (по одному, послідовно) або none (типова поведінка провайдера).",
                ),
                config_description: Some(
                    "Обирає, чи агент об'єднує незалежні виклики інструментів, чи виконує їх по одному.",
                ),
                config_overlay: Some(serde_json::json!({
                    "properties": {
                        "mode": {
                            "title": "Паралельні виклики інструментів",
                            "description": "prefer: запитувати паралельні виклики (типово); avoid: один виклик за хід, послідовно; none: типова поведінка провайдера."
                        }
                    }
                })),
            },
        ]
    }

    async fn system_prompt_contribution(&self, _ctx: &SystemPromptContext) -> Option<String> {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn configuration_validation_and_runtime_preference_agree() {
        let cap = ParallelToolCallsCapability;
        for (config, valid, preference) in [
            (json!(null), true, Some(true)),
            (json!({}), true, Some(true)),
            (json!({"mode":"prefer"}), true, Some(true)),
            (json!({"mode":"avoid"}), true, Some(false)),
            (json!({"mode":"none"}), true, None),
            (json!({"mode":"loud"}), false, None),
            (json!({"mode":"PREFER"}), false, None),
            (json!({"mode":""}), false, None),
            (json!({"mode":null}), false, None),
            (json!({"mode":true}), false, None),
            (json!({"mode":5}), false, None),
            (json!({"mode":{}}), false, None),
            (json!({"mode":[]}), false, None),
            (json!([]), false, None),
            (json!(true), false, None),
            (json!("prefer"), false, None),
        ] {
            assert_eq!(
                cap.validate_config(&config).is_ok(),
                valid,
                "config={config}"
            );
            assert_eq!(
                cap.parallel_tool_calls_preference(&config),
                preference,
                "config={config}"
            );
        }
    }
    #[test]
    fn localizations_resolve_uk() {
        let cap = ParallelToolCallsCapability;
        assert_eq!(
            cap.localized_name(Some("uk-UA")),
            "Паралельні виклики інструментів"
        );
    }
}