//! Feature contribution registry for Pod-hosted builtin/plugin modules. //! //! This module defines the Pod-side feature boundary used to collect //! descriptor metadata, authority requests, tool contributions, safe hook //! contributions, background task declarations, and service declarations before //! installing them into the existing Worker/HookRegistry host surfaces. //! //! The first implementation slice is intentionally host-mediated and //! descriptor-first: tools are installed through the normal Worker tool path, //! hooks are installed through [`crate::hook::HookRegistryBuilder`], while //! service and background-task contributions are represented in descriptors and //! install reports without starting an independent runtime lifecycle. use std::collections::{HashMap, HashSet}; use std::fmt; use std::sync::Arc; use llm_worker::Worker; use llm_worker::llm_client::client::LlmClient; use llm_worker::state::Mutable; use llm_worker::tool::ToolDefinition; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::hook::{Hook, HookRegistryBuilder, OnTurnEnd, PostToolCall, PreLlmRequest, PreToolCall}; /// Stable source-qualified identifier for a feature module. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub struct FeatureId(String); impl FeatureId { pub fn new(value: impl Into) -> Result { let value = value.into(); if value.trim().is_empty() { return Err(FeatureInstallError::InvalidDescriptor( "feature id must not be empty".into(), )); } Ok(Self(value)) } pub fn builtin(slug: impl AsRef) -> Self { Self(format!("builtin:{}", slug.as_ref())) } pub fn as_str(&self) -> &str { &self.0 } } impl fmt::Display for FeatureId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } impl From for String { fn from(value: FeatureId) -> Self { value.0 } } /// Runtime/source class for a feature module. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum FeatureRuntimeKind { Builtin, LuaProfile, ExternalPlugin, } /// Host authority requested by a feature for host-mediated operations that can /// cross sandbox or model-context boundaries. /// /// Contribution declarations such as tools, hooks, background tasks, and /// services are descriptor/package-approved host-visible contributions, not /// sandbox authorities. Grants are additive and do not replace manifest/tool /// permission checks. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum HostAuthority { Filesystem, Network, SecretRef { id: String }, ModelNotification, PodManagement, StateStore { name: String }, ServiceAccess { service: ServiceId }, } /// A safe hook contribution point exposed to feature modules. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum FeatureHookPoint { PreRequest, PreToolCall, ToolResult, TurnEnd, } /// Authority request declared by a feature descriptor. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct AuthorityRequest { pub authority: HostAuthority, pub required: bool, pub reason: String, } impl AuthorityRequest { pub fn required(authority: HostAuthority, reason: impl Into) -> Self { Self { authority, required: true, reason: reason.into(), } } pub fn optional(authority: HostAuthority, reason: impl Into) -> Self { Self { authority, required: false, reason: reason.into(), } } } /// Authority grants resolved by the host for one feature installation. #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct AuthorityGrantSet { granted: HashSet, denied: Vec, } impl AuthorityGrantSet { pub fn grant_all(requests: &[AuthorityRequest]) -> Self { Self { granted: requests .iter() .map(|request| request.authority.clone()) .collect(), denied: Vec::new(), } } pub fn empty() -> Self { Self::default() } pub fn contains(&self, authority: &HostAuthority) -> bool { self.granted.contains(authority) } pub fn denied(&self) -> &[AuthorityDenial] { &self.denied } pub fn grant(&mut self, authority: HostAuthority) { self.granted.insert(authority); } pub fn deny(&mut self, authority: HostAuthority, reason: impl Into) { self.granted.remove(&authority); self.denied.push(AuthorityDenial { authority, reason: reason.into(), }); } } /// Host-side denial of a requested feature authority. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct AuthorityDenial { pub authority: HostAuthority, pub reason: String, } /// Serializable declaration of a tool contribution. The executable factory is /// carried by [`ToolContribution`] during installation. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ToolDeclaration { pub name: String, pub description: String, } impl ToolDeclaration { pub fn new(name: impl Into, description: impl Into) -> Self { Self { name: name.into(), description: description.into(), } } } /// Executable tool contribution wrapper. pub struct ToolContribution { name: String, definition: ToolDefinition, required_authorities: Vec, } impl ToolContribution { pub fn new(name: impl Into, definition: ToolDefinition) -> Self { Self { name: name.into(), definition, required_authorities: Vec::new(), } } pub fn with_required_authorities(mut self, required_authorities: Vec) -> Self { self.required_authorities = required_authorities; self } pub fn name(&self) -> &str { &self.name } } /// Serializable declaration of a hook contribution. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct HookDeclaration { pub name: String, pub point: FeatureHookPoint, } impl HookDeclaration { pub fn new(name: impl Into, point: FeatureHookPoint) -> Self { Self { name: name.into(), point, } } } /// Background task lifecycle phase represented by this registry slice. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum BackgroundTaskLifecycle { DescriptorOnly, HostManaged, } /// Declaration for a feature-provided background task. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BackgroundTaskDeclaration { pub name: String, pub description: String, pub lifecycle: BackgroundTaskLifecycle, } impl BackgroundTaskDeclaration { pub fn descriptor_only(name: impl Into, description: impl Into) -> Self { Self { name: name.into(), description: description.into(), lifecycle: BackgroundTaskLifecycle::DescriptorOnly, } } } /// Source-qualified service identifier. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub struct ServiceId(String); impl ServiceId { pub fn new(value: impl Into) -> Result { let value = value.into(); if value.trim().is_empty() { return Err(FeatureInstallError::InvalidDescriptor( "service id must not be empty".into(), )); } Ok(Self(value)) } pub fn builtin(slug: impl AsRef) -> Self { Self(format!("builtin:{}", slug.as_ref())) } pub fn as_str(&self) -> &str { &self.0 } } impl fmt::Display for ServiceId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } /// Minimal version requirement placeholder for service resolution. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ServiceVersionReq { pub requirement: String, } impl ServiceVersionReq { pub fn any() -> Self { Self { requirement: "*".into(), } } } /// Feature-provided service declaration. This first slice records provider /// metadata and supports requirement matching; it does not expose concrete /// provider objects across feature boundaries. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ServiceDeclaration { pub id: ServiceId, pub version: String, pub description: String, } impl ServiceDeclaration { pub fn new(id: ServiceId, version: impl Into, description: impl Into) -> Self { Self { id, version: version.into(), description: description.into(), } } } /// Feature service requirement used for host-mediated dependency resolution. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ServiceRequirement { pub id: ServiceId, pub version: ServiceVersionReq, pub required: bool, pub reason: String, } impl ServiceRequirement { pub fn required(id: ServiceId, reason: impl Into) -> Self { Self { id, version: ServiceVersionReq::any(), required: true, reason: reason.into(), } } pub fn optional(id: ServiceId, reason: impl Into) -> Self { Self { id, version: ServiceVersionReq::any(), required: false, reason: reason.into(), } } } /// Host-mediated service registry skeleton used during feature installation. #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct FeatureServiceRegistry { providers: HashMap, } impl FeatureServiceRegistry { pub fn providers(&self) -> &HashMap { &self.providers } pub fn provides(&self, id: &ServiceId) -> bool { self.providers.contains_key(id) } fn register_provider( &mut self, feature_id: FeatureId, declaration: ServiceDeclaration, ) -> Result<(), FeatureInstallError> { if let Some(existing) = self.providers.get(&declaration.id) { return Err(FeatureInstallError::DuplicateService { service: declaration.id.to_string(), first_feature: existing.feature_id.to_string(), duplicate_feature: feature_id.to_string(), }); } self.providers.insert( declaration.id.clone(), FeatureServiceProvider { feature_id, declaration, }, ); Ok(()) } } /// Provider metadata for one service declaration. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FeatureServiceProvider { pub feature_id: FeatureId, pub declaration: ServiceDeclaration, } /// Feature descriptor advertised before installation. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FeatureDescriptor { pub id: FeatureId, pub runtime: FeatureRuntimeKind, pub display_name: String, pub version: String, pub description: String, pub requested_authorities: Vec, pub tools: Vec, pub hooks: Vec, pub background_tasks: Vec, pub provides_services: Vec, pub requires_services: Vec, } impl FeatureDescriptor { pub fn builtin(id: impl AsRef, display_name: impl Into) -> Self { Self { id: FeatureId::builtin(id), runtime: FeatureRuntimeKind::Builtin, display_name: display_name.into(), version: env!("CARGO_PKG_VERSION").into(), description: String::new(), requested_authorities: Vec::new(), tools: Vec::new(), hooks: Vec::new(), background_tasks: Vec::new(), provides_services: Vec::new(), requires_services: Vec::new(), } } pub fn with_description(mut self, description: impl Into) -> Self { self.description = description.into(); self } pub fn with_authority(mut self, request: AuthorityRequest) -> Self { self.requested_authorities.push(request); self } pub fn with_tool(mut self, tool: ToolDeclaration) -> Self { self.tools.push(tool); self } pub fn with_hook(mut self, hook: HookDeclaration) -> Self { self.hooks.push(hook); self } pub fn with_background_task(mut self, task: BackgroundTaskDeclaration) -> Self { self.background_tasks.push(task); self } pub fn with_provided_service(mut self, service: ServiceDeclaration) -> Self { self.provides_services.push(service); self } pub fn with_service_requirement(mut self, requirement: ServiceRequirement) -> Self { self.requires_services.push(requirement); self } } /// Feature module contribution boundary. pub trait FeatureModule: Send + Sync { fn descriptor(&self) -> FeatureDescriptor; fn install(&self, context: &mut FeatureInstallContext<'_>) -> Result<(), FeatureInstallError>; } /// Severity for feature installation diagnostics. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum FeatureDiagnosticSeverity { Info, Warning, Error, } /// Installation diagnostic emitted by the feature host or feature module. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FeatureDiagnostic { pub severity: FeatureDiagnosticSeverity, pub message: String, } impl FeatureDiagnostic { pub fn info(message: impl Into) -> Self { Self { severity: FeatureDiagnosticSeverity::Info, message: message.into(), } } pub fn warning(message: impl Into) -> Self { Self { severity: FeatureDiagnosticSeverity::Warning, message: message.into(), } } pub fn error(message: impl Into) -> Self { Self { severity: FeatureDiagnosticSeverity::Error, message: message.into(), } } } /// Kind of contribution represented in install reports. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum FeatureContributionKind { Tool, Hook, BackgroundTask, Service, Notification, Alert, Diagnostic, } /// A contribution intentionally skipped by the host. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SkippedContribution { pub kind: FeatureContributionKind, pub name: String, pub reason: String, } /// Per-feature installation report. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FeatureInstallReport { pub feature_id: FeatureId, pub runtime: FeatureRuntimeKind, pub installed: bool, pub granted_authorities: AuthorityGrantSet, pub installed_tools: Vec, pub installed_hooks: Vec, pub declared_background_tasks: Vec, pub provided_services: Vec, pub resolved_service_requirements: Vec, pub skipped: Vec, pub diagnostics: Vec, } impl FeatureInstallReport { fn new(descriptor: &FeatureDescriptor, granted_authorities: AuthorityGrantSet) -> Self { Self { feature_id: descriptor.id.clone(), runtime: descriptor.runtime.clone(), installed: false, granted_authorities, installed_tools: Vec::new(), installed_hooks: Vec::new(), declared_background_tasks: Vec::new(), provided_services: Vec::new(), resolved_service_requirements: Vec::new(), skipped: Vec::new(), diagnostics: Vec::new(), } } fn mark_skipped( &mut self, kind: FeatureContributionKind, name: impl Into, reason: impl Into, ) { self.skipped.push(SkippedContribution { kind, name: name.into(), reason: reason.into(), }); } } #[derive(Clone, Debug)] struct FeatureContributionDeclarations { tools: HashSet, hooks: HashSet<(String, FeatureHookPoint)>, background_tasks: HashSet, provided_services: HashSet<(ServiceId, String)>, } impl FeatureContributionDeclarations { fn from_descriptor(descriptor: &FeatureDescriptor) -> Self { Self { tools: descriptor .tools .iter() .map(|tool| tool.name.clone()) .collect(), hooks: descriptor .hooks .iter() .map(|hook| (hook.name.clone(), hook.point.clone())) .collect(), background_tasks: descriptor .background_tasks .iter() .map(|task| task.name.clone()) .collect(), provided_services: descriptor .provides_services .iter() .map(|service| (service.id.clone(), service.version.clone())) .collect(), } } fn contains_tool(&self, name: &str) -> bool { self.tools.contains(name) } fn contains_hook(&self, declaration: &HookDeclaration) -> bool { self.hooks .contains(&(declaration.name.clone(), declaration.point.clone())) } fn contains_background_task(&self, declaration: &BackgroundTaskDeclaration) -> bool { self.background_tasks.contains(&declaration.name) } fn contains_provided_service(&self, declaration: &ServiceDeclaration) -> bool { self.provided_services .contains(&(declaration.id.clone(), declaration.version.clone())) } } fn reject_undeclared_contribution( feature_id: &FeatureId, report: &mut FeatureInstallReport, kind: FeatureContributionKind, name: impl Into, ) -> FeatureInstallError { let name = name.into(); let error = FeatureInstallError::UndeclaredContribution { kind: kind.clone(), name: name.clone(), feature: feature_id.to_string(), }; report.mark_skipped(kind, name, error.to_string()); error } fn require_authority( grants: &AuthorityGrantSet, report: &mut FeatureInstallReport, kind: FeatureContributionKind, name: impl Into, authority: &HostAuthority, ) -> Result<(), FeatureInstallError> { if grants.contains(authority) { return Ok(()); } let reason = format!("required authority was not granted: {authority:?}"); report.mark_skipped(kind, name, reason.clone()); Err(FeatureInstallError::AuthorityDenied(reason)) } /// Model-visible durable notification sink skeleton. The first slice exposes /// the boundary without implementing a new event channel. pub struct FeatureNotificationSink<'a> { grants: &'a AuthorityGrantSet, report: &'a mut FeatureInstallReport, } impl FeatureNotificationSink<'_> { pub fn notify_model(&mut self, message: impl Into) -> Result<(), FeatureInstallError> { require_authority( self.grants, self.report, FeatureContributionKind::Notification, "notify_model", &HostAuthority::ModelNotification, )?; let message = message.into(); self.report.diagnostics.push(FeatureDiagnostic::warning(format!( "model notification requested during feature installation but no durable Notify host is attached: {message}" ))); self.report.mark_skipped( FeatureContributionKind::Notification, "notify_model", "durable Notify/SystemItem host is not connected during feature installation", ); Ok(()) } } /// Transient human-facing alert sink skeleton. pub struct FeatureAlertSink<'a> { report: &'a mut FeatureInstallReport, } impl FeatureAlertSink<'_> { pub fn alert(&mut self, message: impl Into) { let message = message.into(); self.report .diagnostics .push(FeatureDiagnostic::info(format!("feature alert: {message}"))); self.report.mark_skipped( FeatureContributionKind::Alert, "alert", "transient alert host is not connected during feature installation", ); } } /// Diagnostic sink available to feature installers. pub struct FeatureDiagnosticSink<'a> { report: &'a mut FeatureInstallReport, } impl FeatureDiagnosticSink<'_> { pub fn push(&mut self, diagnostic: FeatureDiagnostic) { self.report.diagnostics.push(diagnostic); } pub fn info(&mut self, message: impl Into) { self.push(FeatureDiagnostic::info(message)); } pub fn warning(&mut self, message: impl Into) { self.push(FeatureDiagnostic::warning(message)); } pub fn error(&mut self, message: impl Into) { self.push(FeatureDiagnostic::error(message)); } } /// Tool contribution registrar exposed inside [`FeatureInstallContext`]. pub struct ToolContributionRegistrar<'a> { feature_id: &'a FeatureId, declarations: &'a FeatureContributionDeclarations, grants: &'a AuthorityGrantSet, pending_tools: &'a mut Vec, installed_tool_names: &'a mut HashMap, report: &'a mut FeatureInstallReport, } impl ToolContributionRegistrar<'_> { pub fn register(&mut self, contribution: ToolContribution) -> Result<(), FeatureInstallError> { let (tool_meta, tool) = (contribution.definition)(); let model_visible_name = tool_meta.name.clone(); if contribution.name != model_visible_name { let error = FeatureInstallError::ToolNameMismatch { declared: contribution.name, model_visible: model_visible_name.clone(), }; self.report.mark_skipped( FeatureContributionKind::Tool, model_visible_name, error.to_string(), ); return Err(error); } if !self.declarations.contains_tool(&model_visible_name) { return Err(reject_undeclared_contribution( self.feature_id, self.report, FeatureContributionKind::Tool, model_visible_name, )); } for authority in &contribution.required_authorities { require_authority( self.grants, self.report, FeatureContributionKind::Tool, model_visible_name.clone(), authority, )?; } if let Some(first) = self.installed_tool_names.get(&model_visible_name) { let error = FeatureInstallError::DuplicateToolName { tool: model_visible_name.clone(), first_feature: first.to_string(), duplicate_feature: self.feature_id.to_string(), }; self.report.mark_skipped( FeatureContributionKind::Tool, model_visible_name, error.to_string(), ); return Err(error); } self.installed_tool_names .insert(model_visible_name.clone(), self.feature_id.clone()); self.report.installed_tools.push(model_visible_name); self.pending_tools .push(Arc::new(move || (tool_meta.clone(), Arc::clone(&tool)))); Ok(()) } } /// Safe hook contribution registrar backed by [`HookRegistryBuilder`]. pub struct HookContributionRegistrar<'a> { feature_id: &'a FeatureId, declarations: &'a FeatureContributionDeclarations, hook_builder: &'a mut HookRegistryBuilder, report: &'a mut FeatureInstallReport, } impl HookContributionRegistrar<'_> { fn require_declared( &mut self, declaration: &HookDeclaration, ) -> Result<(), FeatureInstallError> { if self.declarations.contains_hook(declaration) { return Ok(()); } Err(reject_undeclared_contribution( self.feature_id, self.report, FeatureContributionKind::Hook, format!("{}:{:?}", declaration.name, declaration.point), )) } pub fn add_pre_request( &mut self, name: impl Into, hook: impl Hook + 'static, ) -> Result<(), FeatureInstallError> { let declaration = HookDeclaration::new(name, FeatureHookPoint::PreRequest); self.require_declared(&declaration)?; self.hook_builder.add_pre_llm_request(hook); self.report.installed_hooks.push(declaration); Ok(()) } pub fn add_pre_tool_call( &mut self, name: impl Into, hook: impl Hook + 'static, ) -> Result<(), FeatureInstallError> { let declaration = HookDeclaration::new(name, FeatureHookPoint::PreToolCall); self.require_declared(&declaration)?; self.hook_builder.add_pre_tool_call(hook); self.report.installed_hooks.push(declaration); Ok(()) } pub fn add_tool_result( &mut self, name: impl Into, hook: impl Hook + 'static, ) -> Result<(), FeatureInstallError> { let declaration = HookDeclaration::new(name, FeatureHookPoint::ToolResult); self.require_declared(&declaration)?; self.hook_builder.add_post_tool_call(hook); self.report.installed_hooks.push(declaration); Ok(()) } pub fn add_turn_end( &mut self, name: impl Into, hook: impl Hook + 'static, ) -> Result<(), FeatureInstallError> { let declaration = HookDeclaration::new(name, FeatureHookPoint::TurnEnd); self.require_declared(&declaration)?; self.hook_builder.add_on_turn_end(hook); self.report.installed_hooks.push(declaration); Ok(()) } } /// Background task registrar for descriptor/report-only contributions. pub struct BackgroundTaskRegistrar<'a> { feature_id: &'a FeatureId, declarations: &'a FeatureContributionDeclarations, report: &'a mut FeatureInstallReport, } impl BackgroundTaskRegistrar<'_> { pub fn declare( &mut self, declaration: BackgroundTaskDeclaration, ) -> Result<(), FeatureInstallError> { if !self.declarations.contains_background_task(&declaration) { return Err(reject_undeclared_contribution( self.feature_id, self.report, FeatureContributionKind::BackgroundTask, declaration.name, )); } if !self .report .declared_background_tasks .iter() .any(|task| task.name == declaration.name) { self.report.declared_background_tasks.push(declaration); } Ok(()) } } /// Service registrar for descriptor/report-only provider metadata. pub struct FeatureServiceRegistrar<'a> { feature_id: &'a FeatureId, declarations: &'a FeatureContributionDeclarations, service_registry: &'a mut FeatureServiceRegistry, report: &'a mut FeatureInstallReport, } impl FeatureServiceRegistrar<'_> { pub fn provide(&mut self, declaration: ServiceDeclaration) -> Result<(), FeatureInstallError> { if !self.declarations.contains_provided_service(&declaration) { return Err(reject_undeclared_contribution( self.feature_id, self.report, FeatureContributionKind::Service, declaration.id.to_string(), )); } if self .report .provided_services .iter() .any(|service| service.id == declaration.id && service.version == declaration.version) { return Ok(()); } self.service_registry .register_provider(self.feature_id.clone(), declaration.clone())?; self.report.provided_services.push(declaration); Ok(()) } } /// Install-time context provided to a feature module. pub struct FeatureInstallContext<'a> { feature_id: &'a FeatureId, declarations: &'a FeatureContributionDeclarations, grants: &'a AuthorityGrantSet, pending_tools: &'a mut Vec, installed_tool_names: &'a mut HashMap, hook_builder: &'a mut HookRegistryBuilder, service_registry: &'a mut FeatureServiceRegistry, report: &'a mut FeatureInstallReport, } impl FeatureInstallContext<'_> { pub fn feature_id(&self) -> &FeatureId { self.feature_id } pub fn grants(&self) -> &AuthorityGrantSet { self.grants } pub fn tools(&mut self) -> ToolContributionRegistrar<'_> { ToolContributionRegistrar { feature_id: self.feature_id, declarations: self.declarations, grants: self.grants, pending_tools: self.pending_tools, installed_tool_names: self.installed_tool_names, report: self.report, } } pub fn hooks(&mut self) -> HookContributionRegistrar<'_> { HookContributionRegistrar { feature_id: self.feature_id, declarations: self.declarations, hook_builder: self.hook_builder, report: self.report, } } pub fn background_tasks(&mut self) -> BackgroundTaskRegistrar<'_> { BackgroundTaskRegistrar { feature_id: self.feature_id, declarations: self.declarations, report: self.report, } } pub fn services(&mut self) -> FeatureServiceRegistrar<'_> { FeatureServiceRegistrar { feature_id: self.feature_id, declarations: self.declarations, service_registry: self.service_registry, report: self.report, } } pub fn notifications(&mut self) -> FeatureNotificationSink<'_> { FeatureNotificationSink { grants: self.grants, report: self.report, } } pub fn alerts(&mut self) -> FeatureAlertSink<'_> { FeatureAlertSink { report: self.report, } } pub fn diagnostics(&mut self) -> FeatureDiagnosticSink<'_> { FeatureDiagnosticSink { report: self.report, } } } /// Aggregate install output for a registry installation. #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct FeatureRegistryInstallReport { pub reports: Vec, pub services: FeatureServiceRegistry, } impl FeatureRegistryInstallReport { pub fn installed_tool_names(&self) -> Vec { self.reports .iter() .flat_map(|report| report.installed_tools.iter().cloned()) .collect() } } /// Builder/installer for enabled feature modules. #[derive(Default)] pub struct FeatureRegistryBuilder { modules: Vec>, } impl FeatureRegistryBuilder { pub fn new() -> Self { Self::default() } pub fn add_module(&mut self, module: M) -> &mut Self where M: FeatureModule + 'static, { self.modules.push(Arc::new(module)); self } pub fn with_module(mut self, module: M) -> Self where M: FeatureModule + 'static, { self.add_module(module); self } pub fn is_empty(&self) -> bool { self.modules.is_empty() } pub fn descriptors(&self) -> Vec { self.modules .iter() .map(|module| module.descriptor()) .collect() } /// Install modules into the existing Worker tool path and hook builder. pub(crate) fn install_into_worker( self, worker: &mut Worker, hook_builder: &mut HookRegistryBuilder, ) -> FeatureRegistryInstallReport { let mut pending_tools = Vec::new(); let report = self.install_into_pending(&mut pending_tools, hook_builder); worker.register_tools(pending_tools); report } pub(crate) fn install_into_pending( self, pending_tools: &mut Vec, hook_builder: &mut HookRegistryBuilder, ) -> FeatureRegistryInstallReport { let descriptors: Vec<_> = self .modules .iter() .map(|module| module.descriptor()) .collect(); let mut service_registry = FeatureServiceRegistry::default(); let mut reports = Vec::with_capacity(self.modules.len()); let mut installed_tool_names = HashMap::new(); let mut seen_features = HashSet::new(); for (module, descriptor) in self.modules.into_iter().zip(descriptors.into_iter()) { let grants = AuthorityGrantSet::grant_all(&descriptor.requested_authorities); let declarations = FeatureContributionDeclarations::from_descriptor(&descriptor); let mut report = FeatureInstallReport::new(&descriptor, grants.clone()); if !seen_features.insert(descriptor.id.clone()) { report.diagnostics.push(FeatureDiagnostic::error(format!( "duplicate feature id: {}", descriptor.id ))); report.mark_skipped( FeatureContributionKind::Diagnostic, descriptor.id.to_string(), "duplicate feature id", ); reports.push(report); continue; } for authority in grants.denied() { report.diagnostics.push(FeatureDiagnostic::warning(format!( "authority denied: {:?}: {}", authority.authority, authority.reason ))); } let mut required_service_failed = false; for requirement in descriptor.requires_services.iter().cloned() { if service_registry.provides(&requirement.id) { report.resolved_service_requirements.push(requirement); } else if requirement.required { let reason = format!( "required service requirement is not available: {}", requirement.id ); report .diagnostics .push(FeatureDiagnostic::error(reason.clone())); report.mark_skipped( FeatureContributionKind::Service, requirement.id.to_string(), reason, ); required_service_failed = true; } else { report.diagnostics.push(FeatureDiagnostic::warning(format!( "optional service requirement is not available: {}", requirement.id ))); report.mark_skipped( FeatureContributionKind::Service, requirement.id.to_string(), "optional service requirement is not available", ); } } if required_service_failed { reports.push(report); continue; } for background_task in descriptor.background_tasks.iter().cloned() { report.declared_background_tasks.push(background_task); } for service in descriptor.provides_services.iter().cloned() { match service_registry.register_provider(descriptor.id.clone(), service.clone()) { Ok(()) => report.provided_services.push(service), Err(error) => { report .diagnostics .push(FeatureDiagnostic::error(error.to_string())); report.mark_skipped( FeatureContributionKind::Service, service.id.to_string(), error.to_string(), ); } } } let install_result = { let mut context = FeatureInstallContext { feature_id: &descriptor.id, declarations: &declarations, grants: &grants, pending_tools, installed_tool_names: &mut installed_tool_names, hook_builder, service_registry: &mut service_registry, report: &mut report, }; module.install(&mut context) }; match install_result { Ok(()) => report.installed = true, Err(error) => { report .diagnostics .push(FeatureDiagnostic::error(error.to_string())); } } reports.push(report); } FeatureRegistryInstallReport { reports, services: service_registry, } } } /// Feature installation errors. #[derive(Debug, Error)] pub enum FeatureInstallError { #[error("invalid feature descriptor: {0}")] InvalidDescriptor(String), #[error( "duplicate tool contribution `{tool}` from feature `{duplicate_feature}`; first registered by `{first_feature}`" )] DuplicateToolName { tool: String, first_feature: String, duplicate_feature: String, }, #[error( "tool contribution declared name `{declared}` does not match model-visible tool name `{model_visible}`" )] ToolNameMismatch { declared: String, model_visible: String, }, #[error( "undeclared {kind:?} contribution `{name}` from feature `{feature}` is not present in the approved feature descriptor" )] UndeclaredContribution { kind: FeatureContributionKind, name: String, feature: String, }, #[error( "duplicate service declaration `{service}` from feature `{duplicate_feature}`; first provided by `{first_feature}`" )] DuplicateService { service: String, first_feature: String, duplicate_feature: String, }, #[error("feature authority denied: {0}")] AuthorityDenied(String), #[error("feature install failed: {0}")] Install(String), } /// Builtin task tools feature used to prove existing builtin tool registration /// through the feature registry without changing tool names, schemas, or /// permission behavior. pub mod builtin { use super::*; pub fn task_feature(task_store: tools::TaskStore) -> impl FeatureModule { TaskFeature { task_store } } struct TaskFeature { task_store: tools::TaskStore, } impl FeatureModule for TaskFeature { fn descriptor(&self) -> FeatureDescriptor { FeatureDescriptor::builtin("task-tools", "Task tools") .with_description("Session-lifetime task tracking builtin tools") .with_tool(ToolDeclaration::new( "TaskCreate", "Create a session-lifetime user-visible task", )) .with_tool(ToolDeclaration::new( "TaskUpdate", "Update a session-lifetime user-visible task", )) .with_tool(ToolDeclaration::new( "TaskGet", "Get one session-lifetime user-visible task", )) .with_tool(ToolDeclaration::new( "TaskList", "List session-lifetime user-visible tasks", )) } fn install( &self, context: &mut FeatureInstallContext<'_>, ) -> Result<(), FeatureInstallError> { let names = ["TaskCreate", "TaskList", "TaskGet", "TaskUpdate"]; for (name, definition) in names .into_iter() .zip(tools::task_tools(self.task_store.clone())) { context .tools() .register(ToolContribution::new(name, definition))?; } Ok(()) } } } #[cfg(test)] mod tests { use super::*; use async_trait::async_trait; use futures::stream; use llm_worker::llm_client::{ClientError, Request, ResponseStream}; use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput}; use serde_json::json; use std::sync::atomic::{AtomicUsize, Ordering}; #[derive(Clone)] struct DummyClient; #[async_trait] impl LlmClient for DummyClient { async fn stream(&self, _request: Request) -> Result { Ok(Box::pin(stream::empty())) } fn clone_boxed(&self) -> Box { Box::new(self.clone()) } } struct DummyTool; #[async_trait] impl Tool for DummyTool { async fn execute(&self, _input_json: &str) -> Result { Ok(ToolOutput::from("ok".to_string())) } } fn dummy_tool(name: &'static str) -> ToolDefinition { Arc::new(move || { ( ToolMeta::new(name) .description("dummy") .input_schema(json!({})), Arc::new(DummyTool) as Arc, ) }) } struct ToolFeature { descriptor: FeatureDescriptor, contribution_name: &'static str, model_visible_name: &'static str, } impl FeatureModule for ToolFeature { fn descriptor(&self) -> FeatureDescriptor { self.descriptor.clone() } fn install( &self, context: &mut FeatureInstallContext<'_>, ) -> Result<(), FeatureInstallError> { context.tools().register(ToolContribution::new( self.contribution_name, dummy_tool(self.model_visible_name), )) } } #[test] fn descriptor_authorities_and_install_report_are_recorded() { let descriptor = FeatureDescriptor::builtin("dummy", "Dummy") .with_tool(ToolDeclaration::new("Dummy", "dummy tool")) .with_background_task(BackgroundTaskDeclaration::descriptor_only( "daily", "descriptor-only background task", )); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(ToolFeature { descriptor, contribution_name: "Dummy", model_visible_name: "Dummy", }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert_eq!(pending_tools.len(), 1); assert_eq!(report.reports.len(), 1); let feature_report = &report.reports[0]; assert!(feature_report.installed); assert_eq!(feature_report.installed_tools, vec!["Dummy"]); assert_eq!(feature_report.declared_background_tasks[0].name, "daily"); assert!(feature_report.granted_authorities.denied().is_empty()); } #[test] fn duplicate_tool_names_are_rejected() { let descriptor_a = FeatureDescriptor::builtin("a", "A") .with_tool(ToolDeclaration::new("Duplicate", "first tool")); let descriptor_b = FeatureDescriptor::builtin("b", "B") .with_tool(ToolDeclaration::new("Duplicate", "second tool")); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(ToolFeature { descriptor: descriptor_a, contribution_name: "Duplicate", model_visible_name: "Duplicate", }) .with_module(ToolFeature { descriptor: descriptor_b, contribution_name: "Duplicate", model_visible_name: "Duplicate", }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert_eq!(pending_tools.len(), 1); assert!(report.reports[0].installed); assert!(!report.reports[1].installed); assert!( report.reports[1] .diagnostics .iter() .any(|diagnostic| diagnostic.message.contains("duplicate tool contribution")) ); assert_eq!( report.reports[1].skipped[0].kind, FeatureContributionKind::Tool ); } #[test] fn mismatched_tool_contribution_name_is_rejected_before_queueing() { let descriptor = FeatureDescriptor::builtin("mismatch", "Mismatch") .with_tool(ToolDeclaration::new("Actual", "actual model-visible tool")); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(ToolFeature { descriptor, contribution_name: "Declared", model_visible_name: "Actual", }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert!(pending_tools.is_empty()); assert!(!report.reports[0].installed); assert!(report.reports[0].diagnostics.iter().any(|diagnostic| { diagnostic .message .contains("does not match model-visible tool name") })); assert_eq!(report.reports[0].skipped[0].name, "Actual"); } #[test] fn stateful_tool_definition_is_materialized_once_for_report_and_worker() { struct StatefulToolFeature { calls: Arc, } impl FeatureModule for StatefulToolFeature { fn descriptor(&self) -> FeatureDescriptor { FeatureDescriptor::builtin("stateful-tool", "Stateful tool") .with_tool(ToolDeclaration::new("First", "stateful tool")) } fn install( &self, context: &mut FeatureInstallContext<'_>, ) -> Result<(), FeatureInstallError> { let calls = Arc::clone(&self.calls); let definition: ToolDefinition = Arc::new(move || { let call_index = calls.fetch_add(1, Ordering::SeqCst); let name = if call_index == 0 { "First" } else { "Second" }; ( ToolMeta::new(name) .description("stateful") .input_schema(json!({})), Arc::new(DummyTool) as Arc, ) }); context .tools() .register(ToolContribution::new("First", definition)) } } let calls = Arc::new(AtomicUsize::new(0)); let mut worker = Worker::new(DummyClient); let mut hook_builder = HookRegistryBuilder::default(); let report = FeatureRegistryBuilder::new() .with_module(StatefulToolFeature { calls: Arc::clone(&calls), }) .install_into_worker(&mut worker, &mut hook_builder); worker.tool_server_handle().flush_pending(); let names: Vec<_> = worker .tool_server_handle() .tool_definitions_sorted() .into_iter() .map(|tool| tool.name) .collect(); assert_eq!(report.installed_tool_names(), vec!["First"]); assert_eq!(names, vec!["First"]); assert_eq!(calls.load(Ordering::SeqCst), 1); } struct ServiceFeature { descriptor: FeatureDescriptor, } impl FeatureModule for ServiceFeature { fn descriptor(&self) -> FeatureDescriptor { self.descriptor.clone() } fn install( &self, _context: &mut FeatureInstallContext<'_>, ) -> Result<(), FeatureInstallError> { Ok(()) } } struct DummyPreToolHook; #[async_trait] impl Hook for DummyPreToolHook { async fn call( &self, _input: &crate::hook::ToolCallSummary, ) -> crate::hook::HookPreToolAction { crate::hook::HookPreToolAction::Continue } } struct HookFeature { descriptor: FeatureDescriptor, hook_name: &'static str, } impl FeatureModule for HookFeature { fn descriptor(&self) -> FeatureDescriptor { self.descriptor.clone() } fn install( &self, context: &mut FeatureInstallContext<'_>, ) -> Result<(), FeatureInstallError> { context .hooks() .add_pre_tool_call(self.hook_name, DummyPreToolHook) } } struct BackgroundFeature { descriptor: FeatureDescriptor, task_name: &'static str, } impl FeatureModule for BackgroundFeature { fn descriptor(&self) -> FeatureDescriptor { self.descriptor.clone() } fn install( &self, context: &mut FeatureInstallContext<'_>, ) -> Result<(), FeatureInstallError> { context .background_tasks() .declare(BackgroundTaskDeclaration::descriptor_only( self.task_name, "runtime background task", )) } } struct ServiceProviderFeature { descriptor: FeatureDescriptor, service: ServiceId, } impl FeatureModule for ServiceProviderFeature { fn descriptor(&self) -> FeatureDescriptor { self.descriptor.clone() } fn install( &self, context: &mut FeatureInstallContext<'_>, ) -> Result<(), FeatureInstallError> { context.services().provide(ServiceDeclaration::new( self.service.clone(), "1", "runtime service provider", )) } } #[test] fn undeclared_tool_contribution_is_rejected() { let descriptor = FeatureDescriptor::builtin("undeclared-tool", "Undeclared tool"); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(ToolFeature { descriptor, contribution_name: "HiddenTool", model_visible_name: "HiddenTool", }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert!(pending_tools.is_empty()); assert!(!report.reports[0].installed); assert_eq!( report.reports[0].skipped[0].kind, FeatureContributionKind::Tool ); assert_eq!(report.reports[0].skipped[0].name, "HiddenTool"); assert!(report.reports[0].diagnostics.iter().any(|diagnostic| { diagnostic .message .contains("is not present in the approved feature descriptor") })); } #[test] fn undeclared_hook_contribution_is_rejected() { let descriptor = FeatureDescriptor::builtin("undeclared-hook", "Undeclared hook"); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(HookFeature { descriptor, hook_name: "hidden-hook", }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert!(!report.reports[0].installed); assert!(report.reports[0].installed_hooks.is_empty()); assert_eq!( report.reports[0].skipped[0].kind, FeatureContributionKind::Hook ); assert!(report.reports[0].skipped[0].name.contains("hidden-hook")); } #[test] fn undeclared_background_task_contribution_is_rejected() { let descriptor = FeatureDescriptor::builtin("undeclared-background", "Undeclared background"); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(BackgroundFeature { descriptor, task_name: "hidden-task", }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert!(!report.reports[0].installed); assert!(report.reports[0].declared_background_tasks.is_empty()); assert_eq!( report.reports[0].skipped[0].kind, FeatureContributionKind::BackgroundTask ); assert_eq!(report.reports[0].skipped[0].name, "hidden-task"); } #[test] fn undeclared_service_provider_contribution_is_rejected() { let service = ServiceId::builtin("hidden-service"); let descriptor = FeatureDescriptor::builtin("undeclared-service", "Undeclared service"); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(ServiceProviderFeature { descriptor, service: service.clone(), }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert!(!report.reports[0].installed); assert!(!report.services.provides(&service)); assert!(report.reports[0].provided_services.is_empty()); assert_eq!( report.reports[0].skipped[0].kind, FeatureContributionKind::Service ); assert_eq!(report.reports[0].skipped[0].name, service.to_string()); } #[test] fn service_requirements_resolve_against_prior_providers() { let service = ServiceId::builtin("demo-service"); let provider = FeatureDescriptor::builtin("provider", "Provider").with_provided_service( ServiceDeclaration::new(service.clone(), "1", "demo service"), ); let consumer = FeatureDescriptor::builtin("consumer", "Consumer") .with_service_requirement(ServiceRequirement::required(service.clone(), "needs demo")); let missing_service = ServiceId::builtin("missing-service"); let missing = FeatureDescriptor::builtin("missing", "Missing").with_service_requirement( ServiceRequirement::required(missing_service, "needs missing"), ); let optional_service = ServiceId::builtin("optional-service"); let optional = FeatureDescriptor::builtin("optional", "Optional").with_service_requirement( ServiceRequirement::optional(optional_service, "nice to have"), ); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(ServiceFeature { descriptor: provider, }) .with_module(ServiceFeature { descriptor: consumer, }) .with_module(ServiceFeature { descriptor: missing, }) .with_module(ServiceFeature { descriptor: optional, }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert!(report.services.provides(&service)); assert!(report.reports[1].installed); assert_eq!( report.reports[1].resolved_service_requirements[0].id, service ); assert!(!report.reports[2].installed); assert!( report.reports[2] .diagnostics .iter() .any(|diagnostic| diagnostic.message.contains("required service requirement")) ); assert!(report.reports[3].installed); assert_eq!( report.reports[3].skipped[0].kind, FeatureContributionKind::Service ); } #[test] fn background_task_declaration_is_not_sandbox_authority_gated() { let descriptor = FeatureDescriptor::builtin("background", "Background") .with_background_task(BackgroundTaskDeclaration::descriptor_only( "declared-task", "descriptor contribution", )); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(ServiceFeature { descriptor }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert!(report.reports[0].installed); assert_eq!( report.reports[0].declared_background_tasks[0].name, "declared-task" ); assert!(report.reports[0].skipped.is_empty()); } #[test] fn service_provider_declaration_is_not_sandbox_authority_gated() { let service = ServiceId::builtin("declared-service"); let descriptor = FeatureDescriptor::builtin("service", "Service").with_provided_service( ServiceDeclaration::new(service.clone(), "1", "descriptor contribution"), ); let mut hook_builder = HookRegistryBuilder::default(); let mut pending_tools = Vec::new(); let report = FeatureRegistryBuilder::new() .with_module(ServiceFeature { descriptor }) .install_into_pending(&mut pending_tools, &mut hook_builder); assert!(report.reports[0].installed); assert!(report.services.provides(&service)); assert_eq!(report.reports[0].provided_services[0].id, service); assert!(report.reports[0].skipped.is_empty()); } #[test] fn builtin_task_feature_installs_through_worker_tool_path() { let task_store = tools::TaskStore::new(); let mut worker = Worker::new(DummyClient); let mut hook_builder = HookRegistryBuilder::default(); let report = FeatureRegistryBuilder::new() .with_module(builtin::task_feature(task_store)) .install_into_worker(&mut worker, &mut hook_builder); worker.tool_server_handle().flush_pending(); let names: Vec<_> = worker .tool_server_handle() .tool_definitions_sorted() .into_iter() .map(|tool| tool.name) .collect(); assert_eq!( names, vec!["TaskCreate", "TaskGet", "TaskList", "TaskUpdate"] ); assert_eq!( report.installed_tool_names(), vec!["TaskCreate", "TaskList", "TaskGet", "TaskUpdate"] ); assert!(report.reports[0].installed); } }