use crate::command::{
CommandDescriptor, CommandExecutionContext, CommandResult, ExecuteCommandRequest,
};
use crate::mcp_server::ScopedMcpServers;
use crate::message_filter::MessageFilterProvider;
use crate::session_files::SessionFileSystem;
use crate::tool_types::{ToolCall, ToolDefinition};
use crate::tools::Tool;
use crate::typed_id::SessionId;
use async_trait::async_trait;
use std::sync::Arc;
mod blueprint;
mod collect;
mod dependencies;
mod registry;
mod spawn_agent;
pub use blueprint::*;
pub use collect::*;
pub use dependencies::*;
pub use registry::*;
pub use spawn_agent::*;
pub struct IntegrationPlugin {
pub experimental_only: bool,
pub feature_flag: Option<&'static str>,
pub factory: fn() -> Box<dyn Capability>,
}
pub use crate::capability_types::{
CapabilityStatus, MountAccess, MountDirectoryBuilder, MountEntry, MountPoint, MountSource,
};
use everruns_capability::{CapabilityId, CapabilityRef as AgentCapabilityConfig};
mod declarative;
pub mod facts;
pub mod skill_contribution;
pub mod util;
pub const A2A_AGENT_DELEGATION_CAPABILITY_ID: &str = "a2a_agent_delegation";
pub const AGENT_RUN_KEY_PREFIX: &str = "agent_run:";
pub const SPAWN_AGENT_CONCURRENCY_CLASS: &str = "spawn_agent";
pub use declarative::{
DECLARATIVE_CAPABILITY_PREFIX, DeclarativeCapabilityDefinition, DeclarativeCapabilityFile,
DeclarativeCapabilitySkill, DeclarativeCapabilitySkillFile, declarative_capability_id,
declarative_capability_info, hydrate_declarative_capability_config,
hydrate_plugin_capability_config, is_declarative_capability, parse_declarative_capability_id,
plugin_capability_info, validate_declarative_capability_definition,
};
pub use facts::{FACTS_DYNAMIC_NOTE, Fact, FactsContext, Volatility, render_facts_block};
pub use skill_contribution::{
MAX_SKILLS_PER_CAPABILITY, SKILL_CAPABILITY_PREFIX, SKILLS_DISCOVERY_PATH,
SkillCapabilityIdExt, SkillContribution, SkillInstructions, SkillMeta, SkillSource,
discover_skills_from_entries, is_skill_capability, parse_skill_capability_id,
reconstruct_skill_md, skill_capability_id,
};
pub struct SystemPromptContext {
pub session_id: SessionId,
pub locale: Option<String>,
pub file_store: Option<Arc<dyn SessionFileSystem>>,
pub model: Option<String>,
pub session_storage: Option<Arc<dyn crate::session_services::SessionStorageStore>>,
}
impl SystemPromptContext {
pub fn without_file_store(session_id: SessionId) -> Self {
Self {
session_id,
locale: None,
file_store: None,
model: None,
session_storage: None,
}
}
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
}
#[derive(Debug, Clone)]
pub struct CapabilityLocalization {
pub locale: &'static str,
pub name: Option<&'static str>,
pub description: Option<&'static str>,
pub config_description: Option<&'static str>,
pub config_overlay: Option<serde_json::Value>,
}
impl CapabilityLocalization {
pub fn text(locale: &'static str, name: &'static str, description: &'static str) -> Self {
Self {
locale,
name: Some(name),
description: Some(description),
config_description: None,
config_overlay: None,
}
}
}
pub fn resolve_localized_field<T>(
localizations: &[CapabilityLocalization],
locale: Option<&str>,
field: impl Fn(&CapabilityLocalization) -> Option<T>,
) -> Option<T> {
let mut candidates: Vec<String> = Vec::new();
if let Some(raw) = locale {
let normalized = raw.trim().replace('_', "-").to_lowercase();
if !normalized.is_empty() {
if let Some((language, _)) = normalized.split_once('-') {
let language = language.to_string();
candidates.push(normalized);
candidates.push(language);
} else {
candidates.push(normalized);
}
}
}
candidates.push("en".to_string());
for candidate in candidates {
let hit = localizations
.iter()
.find(|entry| entry.locale.eq_ignore_ascii_case(&candidate))
.and_then(&field);
if hit.is_some() {
return hit;
}
}
None
}
#[async_trait]
pub trait Capability: Send + Sync {
fn native_async_tools(
&self,
_config: &serde_json::Value,
) -> Option<std::collections::BTreeMap<String, Option<serde_json::Value>>> {
None
}
fn id(&self) -> &str;
fn aliases(&self) -> Vec<&'static str> {
vec![]
}
fn name(&self) -> &str;
fn description(&self) -> &str;
fn localizations(&self) -> Vec<CapabilityLocalization> {
vec![]
}
fn localized_name(&self, locale: Option<&str>) -> String {
resolve_localized_field(&self.localizations(), locale, |entry| entry.name)
.unwrap_or_else(|| self.name())
.to_string()
}
fn localized_description(&self, locale: Option<&str>) -> String {
resolve_localized_field(&self.localizations(), locale, |entry| entry.description)
.unwrap_or_else(|| self.description())
.to_string()
}
fn describe_schema(&self, locale: Option<&str>) -> Option<String> {
resolve_localized_field(&self.localizations(), locale, |entry| {
entry.config_description
})
.map(str::to_string)
}
fn status(&self) -> CapabilityStatus {
CapabilityStatus::Available
}
fn icon(&self) -> Option<&str> {
None
}
fn category(&self) -> Option<&str> {
None
}
fn metadata(&self) -> Option<serde_json::Value> {
None
}
fn is_guardrail(&self) -> bool {
false
}
fn resolve_for_model(&self, _model: Option<&str>) -> Option<&dyn Capability> {
None
}
fn system_prompt_addition(&self) -> Option<&str> {
None
}
async fn system_prompt_contribution(&self, _ctx: &SystemPromptContext) -> Option<String> {
self.system_prompt_addition().map(|addition| {
format!(
"<capability id=\"{}\">\n{}\n</capability>",
self.id(),
addition
)
})
}
fn system_prompt_preview(&self) -> Option<String> {
self.system_prompt_addition().map(|s| s.to_string())
}
fn tools(&self) -> Vec<Box<dyn Tool>> {
vec![]
}
fn tools_with_config(&self, _config: &serde_json::Value) -> Vec<Box<dyn Tool>> {
self.tools()
}
fn delegation_target_with_config(
&self,
_config: &serde_json::Value,
) -> Option<DelegationTargetProvider> {
None
}
fn auto_activates_for(&self, _tool_definitions: &[ToolDefinition]) -> bool {
false
}
async fn system_prompt_contribution_with_config(
&self,
ctx: &SystemPromptContext,
_config: &serde_json::Value,
) -> Option<String> {
self.system_prompt_contribution(ctx).await
}
async fn conversation_context_contribution(
&self,
_ctx: &SystemPromptContext,
) -> Option<String> {
None
}
async fn conversation_context_contribution_with_config(
&self,
ctx: &SystemPromptContext,
_config: &serde_json::Value,
) -> Option<String> {
self.conversation_context_contribution(ctx).await
}
fn tool_definitions(&self) -> Vec<ToolDefinition> {
self.tools().iter().map(|t| t.to_definition()).collect()
}
fn mounts(&self) -> Vec<MountPoint> {
vec![]
}
fn dependencies(&self) -> Vec<&'static str> {
vec![]
}
fn features(&self) -> Vec<&'static str> {
vec![]
}
fn config_schema(&self) -> Option<serde_json::Value> {
None
}
fn config_ui_schema(&self) -> Option<serde_json::Value> {
None
}
fn validate_config(&self, _config: &serde_json::Value) -> Result<(), String> {
Ok(())
}
fn mcp_servers(&self) -> ScopedMcpServers {
ScopedMcpServers::default()
}
fn mcp_servers_with_config(&self, _config: &serde_json::Value) -> ScopedMcpServers {
self.mcp_servers()
}
fn message_filter_provider(&self) -> Option<Arc<dyn MessageFilterProvider>> {
None
}
fn message_filter_config(
&self,
config: &serde_json::Value,
_compaction_enabled: bool,
) -> serde_json::Value {
config.clone()
}
fn model_view_provider(&self) -> Option<Arc<dyn ModelViewProvider>> {
None
}
fn llm_error_hook(&self) -> Option<Arc<dyn crate::llm_error_hook::LlmErrorHook>> {
None
}
fn tool_search_config(
&self,
_config: &serde_json::Value,
) -> Option<crate::driver_registry::ToolSearchConfig> {
None
}
fn prompt_cache_config(
&self,
_config: &serde_json::Value,
) -> Option<crate::driver_registry::PromptCacheConfig> {
None
}
fn driver_options(&self, _config: &serde_json::Value) -> Vec<(String, serde_json::Value)> {
Vec::new()
}
fn parallel_tool_calls_preference(&self, _config: &serde_json::Value) -> Option<bool> {
None
}
fn error_disclosure(
&self,
_config: &serde_json::Value,
) -> Option<crate::user_facing_error::ErrorDisclosure> {
None
}
fn filter_response_text(&self, text: String, _config: &serde_json::Value) -> String {
text
}
fn compaction_policy(
&self,
_config: &serde_json::Value,
) -> Option<Arc<dyn crate::compaction_policy::CompactionPolicy>> {
None
}
fn facts(&self, _config: &serde_json::Value, _ctx: &FactsContext) -> Vec<Fact> {
vec![]
}
fn pre_tool_use_hooks(&self) -> Vec<Arc<dyn crate::tool_hooks::PreToolUseHook>> {
vec![]
}
fn pre_tool_use_hooks_with_config(
&self,
_config: &serde_json::Value,
) -> Vec<Arc<dyn crate::tool_hooks::PreToolUseHook>> {
self.pre_tool_use_hooks()
}
fn post_tool_exec_hooks(&self) -> Vec<Arc<dyn crate::tool_hooks::PostToolExecHook>> {
vec![]
}
fn post_tool_exec_hooks_with_config(
&self,
_config: &serde_json::Value,
) -> Vec<Arc<dyn crate::tool_hooks::PostToolExecHook>> {
self.post_tool_exec_hooks()
}
fn tool_definition_hooks(&self) -> Vec<Arc<dyn ToolDefinitionHook>> {
vec![]
}
fn tool_definition_hooks_with_config(
&self,
_config: &serde_json::Value,
) -> Vec<Arc<dyn ToolDefinitionHook>> {
self.tool_definition_hooks()
}
fn tool_definition_hooks_with_context(
&self,
_ctx: &SystemPromptContext,
config: &serde_json::Value,
) -> Vec<Arc<dyn ToolDefinitionHook>> {
self.tool_definition_hooks_with_config(config)
}
fn tool_call_hooks(&self) -> Vec<Arc<dyn ToolCallHook>> {
vec![]
}
fn finalized_tool_calls_hook(
&self,
_config: &serde_json::Value,
) -> Option<Arc<dyn crate::finalized_tool_calls::FinalizedToolCallsHook>> {
None
}
fn narrate(
&self,
_tool_def: Option<&ToolDefinition>,
tool_call: &ToolCall,
phase: crate::tool_narration::ToolNarrationPhase,
locale: Option<&str>,
ctx: crate::tool_narration::ToolNarrationContext<'_>,
) -> Option<String> {
self.tools()
.iter()
.find(|tool| tool.name() == tool_call.name)
.and_then(|tool| tool.narrate(tool_call, phase, locale, ctx))
}
fn user_hooks(&self) -> Vec<crate::user_hook_types::UserHookSpec> {
vec![]
}
fn user_hooks_with_config(
&self,
_config: &serde_json::Value,
) -> Vec<crate::user_hook_types::UserHookSpec> {
self.user_hooks()
}
fn risk_level(&self) -> RiskLevel {
RiskLevel::Low
}
fn commands(&self) -> Vec<CommandDescriptor> {
vec![]
}
async fn execute_command(
&self,
request: &ExecuteCommandRequest,
_ctx: &CommandExecutionContext,
) -> crate::error::Result<CommandResult> {
Err(crate::error::AgentLoopError::config(format!(
"capability {} declared command /{} but does not implement execute_command",
self.id(),
request.name,
)))
}
fn agent_blueprints(&self) -> Vec<AgentBlueprint> {
vec![]
}
fn contribute_skills(&self) -> Vec<SkillContribution> {
vec![]
}
fn output_guardrails(&self) -> Vec<Arc<dyn crate::output_guardrail::OutputGuardrail>> {
vec![]
}
fn post_output_guardrails_with_config(
&self,
_config: &serde_json::Value,
) -> Vec<Arc<dyn crate::output_guardrail::PostGenerationOutputGuardrail>> {
vec![]
}
fn post_output_annotation_hooks_with_config(
&self,
_config: &serde_json::Value,
) -> Vec<Arc<dyn crate::annotation_hook::PostGenerationAnnotationHook>> {
vec![]
}
fn citation_verifier_with_config(
&self,
_config: &serde_json::Value,
) -> Option<Arc<dyn crate::annotation_hook::CitationVerifier>> {
None
}
}
pub trait ToolDefinitionHook: Send + Sync {
fn transform(&self, tools: Vec<ToolDefinition>) -> Vec<ToolDefinition>;
fn applies_with_native_tool_search(&self) -> bool {
true
}
}
pub trait ToolCallHook: Send + Sync {
fn narration(
&self,
_tool_def: Option<&ToolDefinition>,
_tool_call: &ToolCall,
_phase: crate::tool_narration::ToolNarrationPhase,
_locale: Option<&str>,
_ctx: crate::tool_narration::ToolNarrationContext<'_>,
) -> Option<String> {
None
}
fn transform_for_execution(&self, tool_call: ToolCall) -> ToolCall {
tool_call
}
}
pub struct CapabilityNarrationHook(pub Arc<dyn Capability>);
impl ToolCallHook for CapabilityNarrationHook {
fn narration(
&self,
tool_def: Option<&ToolDefinition>,
tool_call: &ToolCall,
phase: crate::tool_narration::ToolNarrationPhase,
locale: Option<&str>,
ctx: crate::tool_narration::ToolNarrationContext<'_>,
) -> Option<String> {
self.0.narrate(tool_def, tool_call, phase, locale, ctx)
}
}
#[cfg(test)]
mod tests_collect_tests;
#[cfg(test)]
mod tests_registry_tests;
#[cfg(test)]
mod tests_support;