use std::sync::Arc;
use awaken_runtime_contract::StateError;
use awaken_runtime_contract::contract::commit_coordinator::CommitCoordinator;
use awaken_runtime_contract::contract::executor::LlmExecutor;
use awaken_runtime_contract::contract::tool::Tool;
use awaken_runtime_contract::registry_spec::{AgentSpec, ModelPoolSpec, ModelSpec};
#[cfg(feature = "a2a")]
use awaken_runtime_contract::registry_spec::{RemoteAuth, RemoteEndpoint};
#[cfg(feature = "a2a")]
use crate::backend::ExecutionBackendFactory;
use crate::plugins::Plugin;
#[cfg(feature = "a2a")]
use crate::registry::BackendRegistry;
#[cfg(feature = "a2a")]
use crate::registry::composite::{CompositeAgentSpecRegistry, RemoteAgentSource};
#[cfg(feature = "a2a")]
use crate::registry::memory::MapBackendRegistry;
use crate::registry::memory::{
MapAgentSpecRegistry, MapModelRegistry, MapPluginSource, MapProviderRegistry, MapToolRegistry,
};
use crate::registry::snapshot::RegistryHandle;
use crate::registry::traits::{AgentSpecRegistry, RegistrySet};
use crate::runtime::AgentRuntime;
#[derive(Debug, thiserror::Error)]
pub enum BuildError {
#[error("state error: {0}")]
State(#[from] StateError),
#[error("agent registry conflict: {0}")]
AgentRegistryConflict(String),
#[error("tool registry conflict: {0}")]
ToolRegistryConflict(String),
#[error("model registry conflict: {0}")]
ModelRegistryConflict(String),
#[error("provider registry conflict: {0}")]
ProviderRegistryConflict(String),
#[error("plugin registry conflict: {0}")]
PluginRegistryConflict(String),
#[cfg(feature = "a2a")]
#[error("backend registry conflict: {0}")]
BackendRegistryConflict(String),
#[error("agent validation failed: {0}")]
ValidationFailed(String),
#[error(
"ADR-0036 D8: `with_thread_run_store` requires a paired `with_commit_coordinator` \
supplied by the store/server integration"
)]
CommitCoordinatorRequired,
#[error("config validation failed: {0}")]
ConfigValidation(#[from] awaken_runtime_contract::ConfigValidationError),
#[cfg(feature = "a2a")]
#[error("discovery failed: {0}")]
DiscoveryFailed(#[from] crate::registry::composite::DiscoveryError),
}
pub struct AgentRuntimeBuilder {
agents: MapAgentSpecRegistry,
tools: MapToolRegistry,
models: MapModelRegistry,
providers: MapProviderRegistry,
plugins: MapPluginSource,
#[cfg(feature = "a2a")]
backends: MapBackendRegistry,
commit_coordinator: Option<Arc<dyn CommitCoordinator>>,
profile_store: Option<Arc<dyn awaken_runtime_contract::contract::profile_store::ProfileStore>>,
errors: Vec<BuildError>,
#[cfg(feature = "a2a")]
remote_sources: Vec<RemoteAgentSource>,
}
impl AgentRuntimeBuilder {
pub fn new() -> Self {
Self {
agents: MapAgentSpecRegistry::new(),
tools: MapToolRegistry::new(),
models: MapModelRegistry::new(),
providers: MapProviderRegistry::new(),
plugins: MapPluginSource::new(),
#[cfg(feature = "a2a")]
backends: MapBackendRegistry::with_default_remote_backends(),
commit_coordinator: None,
profile_store: None,
errors: Vec::new(),
#[cfg(feature = "a2a")]
remote_sources: Vec::new(),
}
}
pub fn with_agent_spec(mut self, spec: AgentSpec) -> Self {
if let Err(e) = self.agents.register_spec(spec) {
self.errors.push(e);
}
self
}
pub fn with_agent_specs(mut self, specs: impl IntoIterator<Item = AgentSpec>) -> Self {
for spec in specs {
if let Err(e) = self.agents.register_spec(spec) {
self.errors.push(e);
}
}
self
}
pub fn with_tool(mut self, id: impl Into<String>, tool: Arc<dyn Tool>) -> Self {
if let Err(e) = self.tools.register_tool(id, tool) {
self.errors.push(e);
}
self
}
pub fn with_plugin(mut self, id: impl Into<String>, plugin: Arc<dyn Plugin>) -> Self {
if let Err(e) = self.plugins.register_plugin(id, plugin) {
self.errors.push(e);
}
self
}
pub fn with_model(mut self, spec: ModelSpec) -> Self {
if self.models.contains_key(&spec.id) {
self.errors.push(BuildError::ConfigValidation(
awaken_runtime_contract::ConfigValidationError::DuplicateModelId {
id: spec.id.clone(),
},
));
return self;
}
if let Err(e) = self.models.register_model(spec) {
self.errors.push(e);
}
self
}
pub fn with_model_pool(mut self, spec: ModelPoolSpec) -> Self {
if let Err(e) = self.models.register_model_pool(spec) {
self.errors.push(e);
}
self
}
pub fn with_provider(mut self, id: impl Into<String>, executor: Arc<dyn LlmExecutor>) -> Self {
if let Err(e) = self.providers.register_provider(id, executor) {
self.errors.push(e);
}
self
}
pub fn with_mock_provider_profile(
mut self,
profile: crate::engine::MockProviderProfile,
) -> Self {
let provider_id = profile.provider_id.clone();
if let Err(e) = self
.providers
.register_provider(provider_id, profile.executor())
{
self.errors.push(e);
}
if let Err(e) = self.models.register_model(profile.model_spec()) {
self.errors.push(e);
}
self
}
#[cfg(feature = "test-utils")]
pub fn with_in_memory_thread_run_store(
mut self,
store: Arc<awaken_stores::InMemoryStore>,
) -> Self {
let coordinator = awaken_stores::MemoryCommitCoordinator::wrap(store);
self.commit_coordinator = Some(coordinator as Arc<dyn CommitCoordinator>);
self
}
pub fn with_commit_coordinator(mut self, coordinator: Arc<dyn CommitCoordinator>) -> Self {
self.commit_coordinator = Some(coordinator);
self
}
pub fn with_profile_store(
mut self,
store: Arc<dyn awaken_runtime_contract::contract::profile_store::ProfileStore>,
) -> Self {
self.profile_store = Some(store);
self
}
#[cfg(feature = "a2a")]
pub fn with_remote_agents(
mut self,
name: impl Into<String>,
base_url: impl Into<String>,
bearer_token: Option<String>,
) -> Self {
self.remote_sources.push(RemoteAgentSource::from_endpoint(
name,
RemoteEndpoint {
base_url: base_url.into(),
auth: bearer_token.map(RemoteAuth::bearer),
..Default::default()
},
));
self
}
#[cfg(feature = "a2a")]
pub fn with_agent_backend_factory(mut self, factory: Arc<dyn ExecutionBackendFactory>) -> Self {
if let Err(e) = self.backends.register_backend_factory(factory) {
self.errors.push(e);
}
self
}
pub fn build(self) -> Result<AgentRuntime, BuildError> {
let runtime = self.build_unchecked()?;
let resolver = runtime.resolver();
#[cfg(feature = "a2a")]
let registries = runtime.registry_set();
let mut errors = Vec::new();
for agent_id in resolver.agent_ids() {
#[cfg(feature = "a2a")]
{
if let Some(spec) = registries
.as_ref()
.and_then(|set| set.agents.get_agent(&agent_id))
&& spec.uses_remote_backend()
{
let endpoint = match spec.remote_endpoint() {
Ok(Some(endpoint)) => endpoint,
Ok(None) => {
errors.push(format!(
"{agent_id}: invalid remote backend '{}' config",
spec.backend.kind
));
continue;
}
Err(error) => {
errors.push(format!(
"{agent_id}: invalid remote backend '{}' config: {error}",
spec.backend.kind
));
continue;
}
};
let Some(factory) = registries
.as_ref()
.and_then(|set| set.backends.get_backend_factory(&endpoint.backend))
else {
errors.push(format!(
"{agent_id}: unsupported remote backend '{}'",
endpoint.backend
));
continue;
};
if let Err(error) = factory.validate(&endpoint) {
errors.push(format!("{agent_id}: {error}"));
}
continue;
}
}
if let Err(e) = resolver.resolve(&agent_id) {
errors.push(format!("{agent_id}: {e}"));
}
}
if !errors.is_empty() {
return Err(BuildError::ValidationFailed(errors.join("; ")));
}
Ok(runtime)
}
pub fn build_unchecked(mut self) -> Result<AgentRuntime, BuildError> {
if !self.errors.is_empty() {
return Err(self.errors.remove(0));
}
#[cfg(feature = "a2a")]
let (agents, composite_registry): (Arc<dyn AgentSpecRegistry>, _) =
if self.remote_sources.is_empty() {
(Arc::new(self.agents), None)
} else {
let mut composite = CompositeAgentSpecRegistry::new(Arc::new(self.agents));
for source in self.remote_sources {
composite.add_remote(source);
}
let arc = Arc::new(composite);
(Arc::clone(&arc) as Arc<dyn AgentSpecRegistry>, Some(arc))
};
#[cfg(not(feature = "a2a"))]
let agents: Arc<dyn AgentSpecRegistry> = Arc::new(self.agents);
let registry_set = RegistrySet {
agents,
tools: Arc::new(self.tools),
models: Arc::new(self.models),
providers: Arc::new(self.providers),
plugins: Arc::new(self.plugins),
#[cfg(feature = "a2a")]
backends: Arc::new(self.backends) as Arc<dyn BackendRegistry>,
};
let registry_handle = RegistryHandle::new(registry_set.clone());
let resolver_impl = Arc::new(crate::registry::resolve::DynamicRegistryResolver::new(
registry_handle.clone(),
));
let resolver: Arc<dyn crate::registry::AgentResolver> = resolver_impl.clone();
let run_resolver: Arc<dyn crate::resolution::Resolver> = resolver_impl;
let mut runtime = AgentRuntime::new_with_execution_resolver(resolver)
.with_run_resolver(run_resolver)
.with_registry_handle(registry_handle);
#[cfg(feature = "a2a")]
if let Some(composite) = composite_registry {
runtime = runtime.with_composite_registry(composite);
}
if let Some(coordinator) = self.commit_coordinator {
runtime = runtime.with_commit_coordinator(coordinator);
}
if let Some(store) = self.profile_store {
runtime = runtime.with_profile_store(store);
}
Ok(runtime)
}
#[cfg(feature = "a2a")]
pub async fn build_and_discover(self) -> Result<AgentRuntime, BuildError> {
let runtime = self.build_unchecked()?;
if let Some(composite) = runtime.composite_registry() {
composite.discover().await?;
}
Ok(runtime)
}
}
impl Default for AgentRuntimeBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[path = "builder_tests.rs"]
mod tests;