use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::error::RuntimeError;
use crate::execution::SequentialToolExecutor;
use crate::phase::ExecutionEnv;
use crate::plugins::Plugin;
#[cfg(feature = "a2a")]
use crate::registry::ResolvedBackendAgent;
use crate::registry::{AgentResolver, ExecutionResolver, ResolvedAgent, ResolvedExecution};
use awaken_contract::contract::executor::LlmExecutor;
use awaken_contract::contract::tool::Tool;
use crate::registry::snapshot::RegistryHandle;
use crate::registry::traits::RegistrySet;
use awaken_contract::registry_spec::AgentSpec;
use super::error::ResolveError;
pub(crate) fn inject_default_plugins(
mut plugins: Vec<Arc<dyn Plugin>>,
max_rounds: usize,
) -> Vec<Arc<dyn Plugin>> {
plugins.push(Arc::new(
crate::loop_runner::actions::LoopActionHandlersPlugin,
));
plugins.push(Arc::new(crate::policies::MaxRoundsPlugin::new(max_rounds)));
plugins
}
pub(crate) fn resolve_registry_set(
registries: &RegistrySet,
agent_id: &str,
) -> Result<ResolvedAgent, ResolveError> {
let spec = lookup_spec(registries, agent_id)?;
#[cfg(feature = "a2a")]
if spec.endpoint.is_some() {
return Err(ResolveError::RemoteAgentNotDirectlyRunnable(
spec.id.clone(),
));
}
let (executor, upstream_model) = resolve_model_and_executor(registries, &spec)?;
let plugins = build_plugin_chain(registries, &spec)?;
let env = ExecutionEnv::from_plugins(&plugins, &spec.active_hook_filter)?;
let tools = build_tool_set(registries, &spec, &env)?;
let spec_arc = Arc::new(spec);
Ok(ResolvedAgent {
spec: spec_arc,
upstream_model,
tools,
llm_executor: executor,
tool_executor: Arc::new(SequentialToolExecutor),
context_summarizer: None,
background_manager: None,
stream_checkpoint_store: None,
env,
})
}
pub(crate) fn resolve_execution_registry_set(
registries: &RegistrySet,
agent_id: &str,
) -> Result<ResolvedExecution, ResolveError> {
let spec = lookup_spec(registries, agent_id)?;
#[cfg(feature = "a2a")]
if let Some(endpoint) = spec.endpoint.clone() {
let factory = registries
.backends
.get_backend_factory(&endpoint.backend)
.ok_or_else(|| ResolveError::UnsupportedRemoteBackend {
agent_id: spec.id.clone(),
backend: endpoint.backend.clone(),
})?;
factory
.validate(&endpoint)
.map_err(|error| ResolveError::InvalidRemoteEndpointConfig {
agent_id: spec.id.clone(),
backend: endpoint.backend.clone(),
message: error.to_string(),
})?;
return Ok(ResolvedExecution::NonLocal(
ResolvedBackendAgent::with_factory(Arc::new(spec), factory, endpoint),
));
}
resolve_local_spec(registries, spec).map(ResolvedExecution::local)
}
#[cfg(test)]
fn resolve(registries: &RegistrySet, agent_id: &str) -> Result<ResolvedAgent, ResolveError> {
resolve_registry_set(registries, agent_id)
}
fn lookup_spec(registries: &RegistrySet, agent_id: &str) -> Result<AgentSpec, ResolveError> {
registries
.agents
.get_agent(agent_id)
.ok_or_else(|| ResolveError::AgentNotFound(agent_id.into()))
}
fn resolve_local_spec(
registries: &RegistrySet,
spec: AgentSpec,
) -> Result<ResolvedAgent, ResolveError> {
let (executor, upstream_model) = resolve_model_and_executor(registries, &spec)?;
let plugins = build_plugin_chain(registries, &spec)?;
let env = ExecutionEnv::from_plugins(&plugins, &spec.active_hook_filter)?;
let tools = build_tool_set(registries, &spec, &env)?;
let spec_arc = Arc::new(spec);
Ok(ResolvedAgent {
spec: spec_arc,
upstream_model,
tools,
llm_executor: executor,
tool_executor: Arc::new(SequentialToolExecutor),
context_summarizer: None,
background_manager: None,
stream_checkpoint_store: None,
env,
})
}
fn resolve_model_and_executor(
registries: &RegistrySet,
spec: &AgentSpec,
) -> Result<(Arc<dyn LlmExecutor>, String), ResolveError> {
let binding = registries
.models
.get_model(&spec.model_id)
.ok_or_else(|| ResolveError::ModelNotFound(spec.model_id.clone()))?;
let executor = registries
.providers
.get_provider(&binding.provider_id)
.ok_or_else(|| ResolveError::ProviderNotFound(binding.provider_id.clone()))?;
let policy = spec
.config::<crate::engine::RetryConfigKey>()
.map_err(|error| match error {
awaken_contract::StateError::KeyDecode { key, message } => {
ResolveError::InvalidPluginConfig {
plugin: "retry".into(),
key,
message,
}
}
other => ResolveError::EnvBuild(other),
})?;
let executor = if policy.max_retries > 0 || !policy.fallback_upstream_models.is_empty() {
Arc::new(crate::engine::RetryingExecutor::new(executor, policy)) as Arc<dyn LlmExecutor>
} else {
executor
};
Ok((executor, binding.upstream_model.clone()))
}
fn build_plugin_chain(
registries: &RegistrySet,
spec: &AgentSpec,
) -> Result<Vec<Arc<dyn Plugin>>, ResolveError> {
let plugins = resolve_plugins(registries, spec)?;
let mut plugins = inject_default_plugins(plugins, spec.max_rounds);
if let Some(ref policy) = spec.context_policy {
let compaction_config = spec
.config::<crate::context::CompactionConfigKey>()
.unwrap_or_default();
plugins.push(Arc::new(crate::context::CompactionPlugin::new(
compaction_config,
)));
plugins.push(Arc::new(crate::context::ContextTransformPlugin::new(
policy.clone(),
)));
}
validate_sections(spec, &plugins)?;
Ok(plugins)
}
fn build_tool_set(
registries: &RegistrySet,
spec: &AgentSpec,
env: &ExecutionEnv,
) -> Result<HashMap<String, Arc<dyn Tool>>, ResolveError> {
let mut tools = collect_global_tools(registries);
resolve_delegate_tools(registries, spec, &mut tools)?;
for (tool_id, tool) in &env.tools {
if tools.contains_key(tool_id) {
return Err(ResolveError::ToolIdConflict {
tool_id: tool_id.clone(),
source_a: "global".into(),
source_b: "plugin".into(),
});
}
tools.insert(tool_id.clone(), Arc::clone(tool));
}
filter_tools(&mut tools, spec);
Ok(tools)
}
#[cfg_attr(not(feature = "a2a"), allow(unused_variables))]
fn resolve_delegate_tools(
registries: &RegistrySet,
spec: &AgentSpec,
tools: &mut HashMap<String, Arc<dyn Tool>>,
) -> Result<(), ResolveError> {
#[cfg(feature = "a2a")]
if !spec.delegates.is_empty() {
let resolver: Arc<dyn crate::registry::ExecutionResolver> =
Arc::new(RegistrySetResolver::new(registries.clone()));
for delegate_id in &spec.delegates {
let delegate_spec = registries
.agents
.get_agent(delegate_id)
.ok_or_else(|| ResolveError::AgentNotFound(delegate_id.clone()))?;
let description: String = delegate_spec.system_prompt.chars().take(100).collect();
if let Some(endpoint) = &delegate_spec.endpoint {
let factory = registries
.backends
.get_backend_factory(&endpoint.backend)
.ok_or_else(|| ResolveError::UnsupportedRemoteBackend {
agent_id: delegate_id.clone(),
backend: endpoint.backend.clone(),
})?;
factory.validate(endpoint).map_err(|error| {
ResolveError::InvalidRemoteEndpointConfig {
agent_id: delegate_id.clone(),
backend: endpoint.backend.clone(),
message: error.to_string(),
}
})?;
}
let tool: Arc<dyn Tool> =
Arc::new(crate::extensions::a2a::AgentTool::with_execution_resolver(
delegate_id,
&description,
resolver.clone(),
));
let tool_id = tool.descriptor().id;
tools.insert(tool_id, tool);
}
}
#[cfg(not(feature = "a2a"))]
if !spec.delegates.is_empty() {
tracing::warn!(
agent_id = %spec.id,
"agent has delegates but 'a2a' feature is disabled; delegates ignored"
);
}
Ok(())
}
pub struct RegistrySetResolver {
registries: RegistrySet,
}
impl RegistrySetResolver {
pub fn new(registries: RegistrySet) -> Self {
Self { registries }
}
}
impl AgentResolver for RegistrySetResolver {
fn resolve(&self, agent_id: &str) -> Result<ResolvedAgent, RuntimeError> {
resolve_registry_set(&self.registries, agent_id).map_err(|e| RuntimeError::ResolveFailed {
message: e.to_string(),
})
}
fn agent_ids(&self) -> Vec<String> {
self.registries.agents.agent_ids()
}
}
impl ExecutionResolver for RegistrySetResolver {
fn resolve_execution(&self, agent_id: &str) -> Result<ResolvedExecution, RuntimeError> {
resolve_execution_registry_set(&self.registries, agent_id).map_err(|error| {
RuntimeError::ResolveFailed {
message: error.to_string(),
}
})
}
}
pub(crate) struct DynamicRegistryResolver {
handle: RegistryHandle,
}
impl DynamicRegistryResolver {
pub(crate) fn new(handle: RegistryHandle) -> Self {
Self { handle }
}
}
impl AgentResolver for DynamicRegistryResolver {
fn resolve(&self, agent_id: &str) -> Result<ResolvedAgent, RuntimeError> {
let snapshot = self.handle.snapshot();
resolve_registry_set(snapshot.registries(), agent_id).map_err(|e| {
RuntimeError::ResolveFailed {
message: e.to_string(),
}
})
}
fn agent_ids(&self) -> Vec<String> {
self.handle.snapshot().registries().agents.agent_ids()
}
}
impl ExecutionResolver for DynamicRegistryResolver {
fn resolve_execution(&self, agent_id: &str) -> Result<ResolvedExecution, RuntimeError> {
let snapshot = self.handle.snapshot();
resolve_execution_registry_set(snapshot.registries(), agent_id).map_err(|error| {
RuntimeError::ResolveFailed {
message: error.to_string(),
}
})
}
}
fn validate_sections(spec: &AgentSpec, plugins: &[Arc<dyn Plugin>]) -> Result<(), ResolveError> {
let mut claimed_keys: HashSet<&str> = HashSet::new();
for plugin in plugins {
let schemas = plugin.config_schemas();
for schema in &schemas {
claimed_keys.insert(schema.key);
if let Some(value) = spec.sections.get(schema.key) {
jsonschema::validate(&schema.json_schema, value).map_err(|e| {
ResolveError::InvalidPluginConfig {
plugin: plugin.descriptor().name.into(),
key: schema.key.into(),
message: e.to_string(),
}
})?;
}
}
}
for key in spec.sections.keys() {
if !claimed_keys.contains(key.as_str()) {
tracing::warn!(
agent_id = %spec.id,
key = %key,
"section key not claimed by any plugin — possible typo"
);
}
}
Ok(())
}
fn collect_global_tools(registries: &RegistrySet) -> HashMap<String, Arc<dyn Tool>> {
let mut tools = HashMap::new();
for id in registries.tools.tool_ids() {
if let Some(tool) = registries.tools.get_tool(&id) {
tools.insert(id, tool);
}
}
tools
}
fn filter_tools(tools: &mut HashMap<String, Arc<dyn Tool>>, spec: &AgentSpec) {
if let Some(allow) = &spec.allowed_tools {
let allowed: HashSet<&str> = allow.iter().map(|s| s.as_str()).collect();
tools.retain(|id, _| allowed.contains(id.as_str()));
}
if let Some(exclude) = &spec.excluded_tools {
for id in exclude {
tools.remove(id);
}
}
}
fn resolve_plugins(
registries: &RegistrySet,
spec: &AgentSpec,
) -> Result<Vec<Arc<dyn Plugin>>, ResolveError> {
spec.plugin_ids
.iter()
.map(|id| {
registries
.plugins
.get_plugin(id)
.ok_or_else(|| ResolveError::PluginNotFound(id.clone()))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "a2a")]
use crate::extensions::a2a::{
AgentBackend, AgentBackendError, AgentBackendFactory, AgentBackendFactoryError,
DelegateRunResult, DelegateRunStatus,
};
use crate::plugins::{ConfigSchema, PluginDescriptor, PluginRegistrar};
#[cfg(feature = "a2a")]
use crate::registry::BackendRegistry;
#[cfg(feature = "a2a")]
use crate::registry::memory::MapBackendRegistry;
use crate::registry::memory::{
MapAgentSpecRegistry, MapModelRegistry, MapPluginSource, MapProviderRegistry,
MapToolRegistry,
};
use crate::registry::traits::ModelBinding;
use async_trait::async_trait;
use awaken_contract::contract::executor::{InferenceExecutionError, InferenceRequest};
use awaken_contract::contract::inference::{StopReason, StreamResult, TokenUsage};
use awaken_contract::contract::lifecycle::TerminationReason;
use awaken_contract::contract::tool::{
ToolCallContext, ToolDescriptor, ToolError, ToolOutput, ToolResult,
};
#[cfg(feature = "a2a")]
use awaken_contract::registry_spec::RemoteEndpoint;
use serde_json::Value;
#[cfg(feature = "a2a")]
use std::sync::atomic::{AtomicUsize, Ordering};
struct MockTool {
id: String,
}
#[async_trait]
impl Tool for MockTool {
fn descriptor(&self) -> ToolDescriptor {
ToolDescriptor::new(&self.id, &self.id, "mock tool")
}
async fn execute(
&self,
_args: Value,
_ctx: &ToolCallContext,
) -> Result<ToolOutput, ToolError> {
Ok(ToolResult::success(&self.id, Value::Null).into())
}
}
struct MockExecutor;
#[async_trait]
impl LlmExecutor for MockExecutor {
async fn execute(
&self,
_request: InferenceRequest,
) -> Result<StreamResult, InferenceExecutionError> {
Ok(StreamResult {
content: vec![],
tool_calls: vec![],
usage: Some(TokenUsage::default()),
stop_reason: Some(StopReason::EndTurn),
has_incomplete_tool_calls: false,
})
}
fn name(&self) -> &str {
"mock"
}
}
#[cfg(feature = "a2a")]
struct StaticBackend {
result: DelegateRunResult,
}
#[cfg(feature = "a2a")]
#[async_trait]
impl AgentBackend for StaticBackend {
async fn execute_root(
&self,
_request: crate::backend::BackendRootRunRequest<'_>,
) -> Result<DelegateRunResult, AgentBackendError> {
Ok(self.result.clone())
}
async fn execute_delegate(
&self,
_request: crate::backend::BackendDelegateRunRequest<'_>,
) -> Result<DelegateRunResult, AgentBackendError> {
Ok(self.result.clone())
}
}
#[cfg(feature = "a2a")]
struct StaticBackendFactory {
backend: &'static str,
result: DelegateRunResult,
validate_count: Arc<AtomicUsize>,
build_count: Arc<AtomicUsize>,
}
#[cfg(feature = "a2a")]
impl AgentBackendFactory for StaticBackendFactory {
fn backend(&self) -> &str {
self.backend
}
fn validate(&self, endpoint: &RemoteEndpoint) -> Result<(), AgentBackendFactoryError> {
self.validate_count.fetch_add(1, Ordering::SeqCst);
if endpoint.backend != self.backend {
return Err(AgentBackendFactoryError::InvalidConfig(format!(
"unexpected backend {}",
endpoint.backend
)));
}
Ok(())
}
fn build(
&self,
endpoint: &RemoteEndpoint,
) -> Result<Arc<dyn AgentBackend>, AgentBackendFactoryError> {
self.build_count.fetch_add(1, Ordering::SeqCst);
if endpoint.backend != self.backend {
return Err(AgentBackendFactoryError::InvalidConfig(format!(
"unexpected backend {}",
endpoint.backend
)));
}
Ok(Arc::new(StaticBackend {
result: self.result.clone(),
}))
}
}
struct MockPlugin {
name: &'static str,
}
impl Plugin for MockPlugin {
fn descriptor(&self) -> PluginDescriptor {
PluginDescriptor { name: self.name }
}
}
fn build_registries(
tools: Vec<(&str, Arc<dyn Tool>)>,
model_id: &str,
model_binding: ModelBinding,
provider_id: &str,
executor: Arc<dyn LlmExecutor>,
plugins: Vec<(&str, Arc<dyn Plugin>)>,
spec: AgentSpec,
) -> RegistrySet {
let mut tool_reg = MapToolRegistry::new();
for (id, tool) in tools {
tool_reg
.register_tool(id, tool)
.expect("duplicate tool in test");
}
let mut model_reg = MapModelRegistry::new();
model_reg
.register_model(model_id, model_binding)
.expect("duplicate model in test");
let mut provider_reg = MapProviderRegistry::new();
provider_reg
.register_provider(provider_id, executor)
.expect("duplicate provider in test");
let mut plugin_reg = MapPluginSource::new();
for (id, plugin) in plugins {
plugin_reg
.register_plugin(id, plugin)
.expect("duplicate plugin in test");
}
let mut agent_reg = MapAgentSpecRegistry::new();
agent_reg
.register_spec(spec)
.expect("duplicate agent in test");
RegistrySet {
agents: Arc::new(agent_reg),
tools: Arc::new(tool_reg),
models: Arc::new(model_reg),
providers: Arc::new(provider_reg),
plugins: Arc::new(plugin_reg),
#[cfg(feature = "a2a")]
backends: Arc::new(MapBackendRegistry::with_default_remote_backends())
as Arc<dyn BackendRegistry>,
}
}
fn make_spec(id: &str) -> AgentSpec {
AgentSpec {
id: id.into(),
model_id: "test-model".into(),
system_prompt: "You are helpful.".into(),
..Default::default()
}
}
#[test]
fn resolve_happy_path() {
let spec = AgentSpec {
plugin_ids: vec!["log".into()],
..make_spec("agent-1")
};
let regs = build_registries(
vec![
("read", Arc::new(MockTool { id: "read".into() })),
("write", Arc::new(MockTool { id: "write".into() })),
],
"test-model",
ModelBinding {
provider_id: "anthropic".into(),
upstream_model: "claude-opus-4-20250514".into(),
},
"anthropic",
Arc::new(MockExecutor),
vec![("log", Arc::new(MockPlugin { name: "log" }))],
spec,
);
let run = resolve(®s, "agent-1").unwrap();
assert_eq!(run.id(), "agent-1");
assert_eq!(run.upstream_model, "claude-opus-4-20250514");
assert_eq!(run.tools.len(), 2);
assert!(run.tools.contains_key("read"));
assert!(run.tools.contains_key("write"));
assert_eq!(run.env.plugins.len(), 3); }
#[test]
fn resolve_agent_not_found() {
let regs = build_registries(
vec![],
"m",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
make_spec("existing"),
);
let err = resolve(®s, "missing").unwrap_err();
assert!(matches!(err, ResolveError::AgentNotFound(ref id) if id == "missing"));
assert!(err.to_string().contains("missing"));
}
#[test]
fn resolve_remote_agent_returns_error() {
use awaken_contract::registry_spec::RemoteEndpoint;
let spec = AgentSpec {
endpoint: Some(RemoteEndpoint {
backend: "a2a".into(),
base_url: "https://remote.example.com".into(),
..Default::default()
}),
..make_spec("remote-agent")
};
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let err = resolve(®s, "remote-agent").unwrap_err();
assert!(
matches!(err, ResolveError::RemoteAgentNotDirectlyRunnable(ref id) if id == "remote-agent")
);
assert!(err.to_string().contains("remote-agent"));
assert!(err.to_string().contains("cannot be resolved locally"));
}
#[cfg(feature = "a2a")]
#[test]
fn resolve_delegate_rejects_unknown_remote_backend() {
use awaken_contract::registry_spec::RemoteEndpoint;
let root = AgentSpec {
delegates: vec!["remote-worker".into()],
..make_spec("root")
};
let remote = AgentSpec {
id: "remote-worker".into(),
endpoint: Some(RemoteEndpoint {
backend: "acp".into(),
base_url: "https://remote.example.com".into(),
..Default::default()
}),
..make_spec("remote-worker")
};
let mut model_reg = MapModelRegistry::new();
model_reg
.register_model(
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
)
.unwrap();
let mut provider_reg = MapProviderRegistry::new();
provider_reg
.register_provider("p", Arc::new(MockExecutor))
.unwrap();
let mut agent_reg = MapAgentSpecRegistry::new();
agent_reg.register_spec(root).unwrap();
agent_reg.register_spec(remote).unwrap();
let regs = RegistrySet {
agents: Arc::new(agent_reg),
tools: Arc::new(MapToolRegistry::new()),
models: Arc::new(model_reg),
providers: Arc::new(provider_reg),
plugins: Arc::new(MapPluginSource::new()),
backends: Arc::new(MapBackendRegistry::with_default_remote_backends())
as Arc<dyn BackendRegistry>,
};
let err = resolve(®s, "root").unwrap_err();
assert!(matches!(
err,
ResolveError::UnsupportedRemoteBackend {
ref agent_id,
ref backend,
} if agent_id == "remote-worker" && backend == "acp"
));
}
#[cfg(feature = "a2a")]
#[tokio::test]
async fn resolve_delegate_uses_registered_backend_factory() {
let validate_count = Arc::new(AtomicUsize::new(0));
let build_count = Arc::new(AtomicUsize::new(0));
let root = AgentSpec {
delegates: vec!["remote-worker".into()],
..make_spec("root")
};
let remote = AgentSpec {
id: "remote-worker".into(),
endpoint: Some(RemoteEndpoint {
backend: "test-backend".into(),
base_url: "https://remote.example.com".into(),
..Default::default()
}),
..make_spec("remote-worker")
};
let mut model_reg = MapModelRegistry::new();
model_reg
.register_model(
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
)
.unwrap();
let mut provider_reg = MapProviderRegistry::new();
provider_reg
.register_provider("p", Arc::new(MockExecutor))
.unwrap();
let mut agent_reg = MapAgentSpecRegistry::new();
agent_reg.register_spec(root).unwrap();
agent_reg.register_spec(remote).unwrap();
let mut backends = MapBackendRegistry::with_default_remote_backends();
backends
.register_backend_factory(Arc::new(StaticBackendFactory {
backend: "test-backend",
result: DelegateRunResult {
agent_id: "remote-worker".into(),
status: DelegateRunStatus::Completed,
termination: TerminationReason::NaturalEnd,
status_reason: None,
response: Some("from custom backend".into()),
output: crate::backend::BackendRunOutput::from_text(Some(
"from custom backend".into(),
)),
steps: 1,
run_id: None,
inbox: None,
state: None,
},
validate_count: validate_count.clone(),
build_count: build_count.clone(),
}))
.unwrap();
let regs = RegistrySet {
agents: Arc::new(agent_reg),
tools: Arc::new(MapToolRegistry::new()),
models: Arc::new(model_reg),
providers: Arc::new(provider_reg),
plugins: Arc::new(MapPluginSource::new()),
backends: Arc::new(backends) as Arc<dyn BackendRegistry>,
};
let run = resolve(®s, "root").unwrap();
assert_eq!(validate_count.load(Ordering::SeqCst), 1);
assert_eq!(build_count.load(Ordering::SeqCst), 0);
let tool = run.tools.get("agent_run_remote-worker").unwrap();
let output = tool
.execute(
serde_json::json!({ "prompt": "delegate this" }),
&ToolCallContext::test_default(),
)
.await
.unwrap();
assert!(output.result.is_success());
assert_eq!(output.result.data["response"], "from custom backend");
assert_eq!(validate_count.load(Ordering::SeqCst), 2);
assert_eq!(build_count.load(Ordering::SeqCst), 1);
}
#[test]
fn resolve_model_not_found() {
let mut spec = make_spec("a");
spec.model_id = "nonexistent-model".into();
let regs = build_registries(
vec![],
"other-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let err = resolve(®s, "a").unwrap_err();
assert!(matches!(err, ResolveError::ModelNotFound(ref id) if id == "nonexistent-model"));
}
#[test]
fn resolve_provider_not_found() {
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "missing-provider".into(),
upstream_model: "n".into(),
},
"other-provider",
Arc::new(MockExecutor),
vec![],
make_spec("a"),
);
let err = resolve(®s, "a").unwrap_err();
assert!(matches!(err, ResolveError::ProviderNotFound(ref id) if id == "missing-provider"));
}
#[test]
fn resolve_invalid_retry_config_fails() {
let spec = make_spec("a").with_section(
"retry",
serde_json::json!({
"max_retries": "not-a-number",
"fallback_upstream_models": []
}),
);
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let err = resolve(®s, "a").unwrap_err();
match err {
ResolveError::InvalidPluginConfig {
plugin,
key,
message,
} => {
assert_eq!(plugin, "retry");
assert_eq!(key, "retry");
assert!(!message.is_empty(), "expected non-empty error message");
}
other => panic!("expected InvalidPluginConfig, got: {other:?}"),
}
}
#[test]
fn resolve_plugin_not_found() {
let spec = AgentSpec {
plugin_ids: vec!["missing-plugin".into()],
..make_spec("a")
};
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let err = resolve(®s, "a").unwrap_err();
assert!(matches!(err, ResolveError::PluginNotFound(ref id) if id == "missing-plugin"));
}
#[test]
fn resolve_tool_allow_list() {
let spec = AgentSpec {
allowed_tools: Some(vec!["read".into()]),
..make_spec("a")
};
let regs = build_registries(
vec![
("read", Arc::new(MockTool { id: "read".into() })),
("write", Arc::new(MockTool { id: "write".into() })),
(
"delete",
Arc::new(MockTool {
id: "delete".into(),
}),
),
],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.tools.len(), 1);
assert!(run.tools.contains_key("read"));
}
#[test]
fn resolve_tool_exclude_list() {
let spec = AgentSpec {
excluded_tools: Some(vec!["delete".into()]),
..make_spec("a")
};
let regs = build_registries(
vec![
("read", Arc::new(MockTool { id: "read".into() })),
("write", Arc::new(MockTool { id: "write".into() })),
(
"delete",
Arc::new(MockTool {
id: "delete".into(),
}),
),
],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.tools.len(), 2);
assert!(run.tools.contains_key("read"));
assert!(run.tools.contains_key("write"));
assert!(!run.tools.contains_key("delete"));
}
#[test]
fn resolve_tool_allow_and_exclude_combined() {
let spec = AgentSpec {
allowed_tools: Some(vec!["read".into(), "write".into(), "delete".into()]),
excluded_tools: Some(vec!["delete".into()]),
..make_spec("a")
};
let regs = build_registries(
vec![
("read", Arc::new(MockTool { id: "read".into() })),
("write", Arc::new(MockTool { id: "write".into() })),
(
"delete",
Arc::new(MockTool {
id: "delete".into(),
}),
),
("exec", Arc::new(MockTool { id: "exec".into() })),
],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.tools.len(), 2);
assert!(run.tools.contains_key("read"));
assert!(run.tools.contains_key("write"));
assert!(!run.tools.contains_key("delete"));
assert!(!run.tools.contains_key("exec"));
}
#[test]
fn resolve_empty_plugins_yields_empty_env() {
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
make_spec("a"),
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.env.plugins.len(), 2); }
#[test]
fn resolve_error_display_strings() {
let cases = vec![
(
ResolveError::AgentNotFound("x".into()),
"agent not found: x",
),
(
ResolveError::ModelNotFound("y".into()),
"model not found: y",
),
(
ResolveError::ProviderNotFound("z".into()),
"provider not found: z",
),
(
ResolveError::PluginNotFound("w".into()),
"plugin not found: w",
),
(
ResolveError::RemoteAgentNotDirectlyRunnable("r".into()),
"remote agent `r` cannot be resolved locally — use it as a delegate instead",
),
(
ResolveError::ToolIdConflict {
tool_id: "my_tool".into(),
source_a: "global".into(),
source_b: "plugin".into(),
},
"tool ID conflict: \"my_tool\" registered by both global and plugin",
),
];
for (err, expected) in cases {
assert_eq!(err.to_string(), expected);
}
}
#[test]
fn registry_set_resolver_resolves_agent() {
use crate::registry::AgentResolver;
let regs = build_registries(
vec![
("read", Arc::new(MockTool { id: "read".into() })),
("write", Arc::new(MockTool { id: "write".into() })),
],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "claude-test".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
make_spec("my-agent"),
);
let resolver = RegistrySetResolver::new(regs);
let resolved = resolver.resolve("my-agent").unwrap();
assert_eq!(resolved.id(), "my-agent");
assert_eq!(resolved.model_id(), "test-model");
assert_eq!(resolved.upstream_model, "claude-test");
assert_eq!(resolved.system_prompt(), "You are helpful.");
assert_eq!(resolved.tools.len(), 2);
assert!(resolved.tools.contains_key("read"));
}
#[test]
fn registry_set_resolver_not_found() {
use crate::registry::AgentResolver;
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
make_spec("existing"),
);
let resolver = RegistrySetResolver::new(regs);
let err = resolver.resolve("missing").unwrap_err();
assert!(matches!(err, RuntimeError::ResolveFailed { .. }));
}
struct ValidatedPlugin {
name: &'static str,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
struct ValidatedConfig {
pub mode: String,
pub threshold: u32,
}
struct ValidatedConfigKey;
impl awaken_contract::PluginConfigKey for ValidatedConfigKey {
const KEY: &'static str = "validated";
type Config = ValidatedConfig;
}
impl Plugin for ValidatedPlugin {
fn descriptor(&self) -> PluginDescriptor {
PluginDescriptor { name: self.name }
}
fn config_schemas(&self) -> Vec<ConfigSchema> {
vec![ConfigSchema::for_key::<ValidatedConfigKey>()]
}
}
#[test]
fn validate_sections_valid_config_passes() {
let spec = AgentSpec {
plugin_ids: vec!["vp".into()],
..make_spec("a")
}
.with_section(
"validated",
serde_json::json!({"mode": "strict", "threshold": 42}),
);
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("vp", Arc::new(ValidatedPlugin { name: "vp" }))],
spec,
);
let run = resolve(®s, "a");
assert!(run.is_ok());
}
#[test]
fn validate_sections_invalid_config_fails() {
let spec = AgentSpec {
plugin_ids: vec!["vp".into()],
..make_spec("a")
}
.with_section(
"validated",
serde_json::json!({"mode": 123, "threshold": "not_a_number"}),
);
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("vp", Arc::new(ValidatedPlugin { name: "vp" }))],
spec,
);
let err = resolve(®s, "a").unwrap_err();
match err {
ResolveError::InvalidPluginConfig {
plugin,
key,
message,
} => {
assert_eq!(plugin, "vp");
assert_eq!(key, "validated");
assert!(!message.is_empty(), "expected non-empty error message");
}
other => panic!("expected InvalidPluginConfig, got: {other:?}"),
}
}
#[test]
fn validate_sections_missing_section_is_ok() {
let spec = AgentSpec {
plugin_ids: vec!["vp".into()],
..make_spec("a")
};
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("vp", Arc::new(ValidatedPlugin { name: "vp" }))],
spec,
);
assert!(resolve(®s, "a").is_ok());
}
#[test]
fn validate_sections_no_schema_plugin_still_works() {
let spec = AgentSpec {
plugin_ids: vec!["log".into()],
..make_spec("a")
}
.with_section("random_key", serde_json::json!({"anything": true}));
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("log", Arc::new(MockPlugin { name: "log" }))],
spec,
);
assert!(resolve(®s, "a").is_ok());
}
struct ToolPlugin {
name: &'static str,
tool_id: &'static str,
}
impl Plugin for ToolPlugin {
fn descriptor(&self) -> PluginDescriptor {
PluginDescriptor { name: self.name }
}
fn register(
&self,
registrar: &mut PluginRegistrar,
) -> Result<(), awaken_contract::StateError> {
registrar.register_tool(
self.tool_id,
Arc::new(MockTool {
id: self.tool_id.into(),
}),
)?;
Ok(())
}
}
#[test]
fn resolve_plugin_registered_tools_are_available() {
let spec = AgentSpec {
plugin_ids: vec!["tp".into()],
..make_spec("a")
};
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![(
"tp",
Arc::new(ToolPlugin {
name: "tp",
tool_id: "plugin_tool",
}),
)],
spec,
);
let run = resolve(®s, "a").unwrap();
assert!(run.tools.contains_key("plugin_tool"));
}
#[test]
fn resolve_plugin_tool_conflict_with_global_tool() {
let spec = AgentSpec {
plugin_ids: vec!["tp".into()],
..make_spec("a")
};
let regs = build_registries(
vec![(
"conflicting",
Arc::new(MockTool {
id: "conflicting".into(),
}),
)],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![(
"tp",
Arc::new(ToolPlugin {
name: "tp",
tool_id: "conflicting",
}),
)],
spec,
);
let err = resolve(®s, "a").unwrap_err();
assert!(matches!(
err,
ResolveError::ToolIdConflict {
ref tool_id,
..
} if tool_id == "conflicting"
));
}
#[test]
fn resolve_plugin_tools_respect_exclude_filter() {
let spec = AgentSpec {
plugin_ids: vec!["tp".into()],
excluded_tools: Some(vec!["plugin_tool".into()]),
..make_spec("a")
};
let regs = build_registries(
vec![(
"global_tool",
Arc::new(MockTool {
id: "global_tool".into(),
}),
)],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![(
"tp",
Arc::new(ToolPlugin {
name: "tp",
tool_id: "plugin_tool",
}),
)],
spec,
);
let run = resolve(®s, "a").unwrap();
assert!(!run.tools.contains_key("plugin_tool"));
assert!(run.tools.contains_key("global_tool"));
}
#[test]
fn resolve_plugin_tools_respect_allow_filter() {
let spec = AgentSpec {
plugin_ids: vec!["tp".into()],
allowed_tools: Some(vec!["plugin_tool".into()]),
..make_spec("a")
};
let regs = build_registries(
vec![(
"global_tool",
Arc::new(MockTool {
id: "global_tool".into(),
}),
)],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![(
"tp",
Arc::new(ToolPlugin {
name: "tp",
tool_id: "plugin_tool",
}),
)],
spec,
);
let run = resolve(®s, "a").unwrap();
assert!(run.tools.contains_key("plugin_tool"));
assert!(!run.tools.contains_key("global_tool"));
}
#[test]
fn resolve_multiple_plugins_all_loaded() {
let spec = AgentSpec {
plugin_ids: vec!["p1".into(), "p2".into(), "p3".into()],
..make_spec("a")
};
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![
("p1", Arc::new(MockPlugin { name: "p1" })),
("p2", Arc::new(MockPlugin { name: "p2" })),
("p3", Arc::new(MockPlugin { name: "p3" })),
],
spec,
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.env.plugins.len(), 5);
}
#[test]
fn resolve_no_tools_yields_empty_tool_set() {
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
make_spec("a"),
);
let run = resolve(®s, "a").unwrap();
assert!(run.tools.is_empty());
}
#[test]
fn resolve_spec_max_rounds_propagated() {
let spec = AgentSpec {
max_rounds: 42,
..make_spec("a")
};
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.max_rounds(), 42);
}
#[test]
fn resolve_spec_system_prompt_propagated() {
let spec = AgentSpec {
system_prompt: "Custom instructions for the agent.".into(),
..make_spec("a")
};
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.system_prompt(), "Custom instructions for the agent.");
}
#[test]
fn resolve_upstream_model_from_model_binding() {
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "claude-opus-4-20250514".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
make_spec("a"),
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.upstream_model, "claude-opus-4-20250514");
}
#[test]
fn resolve_empty_allow_list_removes_all_tools() {
let spec = AgentSpec {
allowed_tools: Some(vec![]),
..make_spec("a")
};
let regs = build_registries(
vec![
("read", Arc::new(MockTool { id: "read".into() })),
("write", Arc::new(MockTool { id: "write".into() })),
],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let run = resolve(®s, "a").unwrap();
assert!(run.tools.is_empty(), "empty allow list removes all tools");
}
#[test]
fn resolve_exclude_nonexistent_tool_is_noop() {
let spec = AgentSpec {
excluded_tools: Some(vec!["nonexistent".into()]),
..make_spec("a")
};
let regs = build_registries(
vec![("read", Arc::new(MockTool { id: "read".into() }))],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let run = resolve(®s, "a").unwrap();
assert_eq!(run.tools.len(), 1);
assert!(run.tools.contains_key("read"));
}
#[test]
fn resolve_default_spec_has_expected_defaults() {
let spec = make_spec("default-agent");
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![],
spec,
);
let run = resolve(®s, "default-agent").unwrap();
assert_eq!(run.max_rounds(), 16);
assert_eq!(run.max_continuation_retries(), 2);
assert!(run.context_policy().is_none());
assert!(run.spec.allowed_tools.is_none());
assert!(run.spec.excluded_tools.is_none());
assert!(run.spec.plugin_ids.is_empty());
assert!(run.spec.delegates.is_empty());
}
#[test]
fn resolver_agent_ids_returns_all_registered() {
use crate::registry::AgentResolver;
let mut agent_reg = MapAgentSpecRegistry::new();
agent_reg.register_spec(make_spec("a1")).unwrap();
agent_reg.register_spec(make_spec("a2")).unwrap();
agent_reg.register_spec(make_spec("a3")).unwrap();
let mut model_reg = MapModelRegistry::new();
model_reg
.register_model(
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
)
.unwrap();
let mut provider_reg = MapProviderRegistry::new();
provider_reg
.register_provider("p", Arc::new(MockExecutor))
.unwrap();
let regs = RegistrySet {
agents: Arc::new(agent_reg),
tools: Arc::new(MapToolRegistry::new()),
models: Arc::new(model_reg),
providers: Arc::new(provider_reg),
plugins: Arc::new(MapPluginSource::new()),
#[cfg(feature = "a2a")]
backends: Arc::new(MapBackendRegistry::with_default_remote_backends())
as Arc<dyn BackendRegistry>,
};
let resolver = RegistrySetResolver::new(regs);
let mut ids = resolver.agent_ids();
ids.sort();
assert_eq!(ids, vec!["a1", "a2", "a3"]);
}
#[test]
fn inject_default_plugins_adds_required_plugins() {
let plugins = inject_default_plugins(vec![], 10);
assert_eq!(plugins.len(), 2);
let names: Vec<&str> = plugins.iter().map(|p| p.descriptor().name).collect();
assert!(names.contains(&"__loop_action_handlers"));
assert!(names.contains(&"stop-condition:max-rounds"));
}
struct StatefulPlugin {
name: &'static str,
}
#[derive(
Debug,
Clone,
Default,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
schemars::JsonSchema,
)]
struct StatefulPluginConfig {
pub level: String,
pub max_items: u32,
}
struct StatefulPluginConfigKey;
impl awaken_contract::PluginConfigKey for StatefulPluginConfigKey {
const KEY: &'static str = "stateful";
type Config = StatefulPluginConfig;
}
impl Plugin for StatefulPlugin {
fn descriptor(&self) -> PluginDescriptor {
PluginDescriptor { name: self.name }
}
fn config_schemas(&self) -> Vec<ConfigSchema> {
vec![ConfigSchema::for_key::<StatefulPluginConfigKey>()]
}
fn on_activate(
&self,
agent_spec: &AgentSpec,
_patch: &mut crate::state::MutationBatch,
) -> Result<(), awaken_contract::StateError> {
let _config = agent_spec.config::<StatefulPluginConfigKey>()?;
Ok(())
}
}
fn stateful_config() -> serde_json::Value {
serde_json::json!({"level": "debug", "max_items": 100})
}
#[test]
fn config_sections_preserved_when_plugin_removed_from_plugin_ids() {
let spec = AgentSpec {
plugin_ids: vec![], ..make_spec("a")
}
.with_section("stateful", stateful_config());
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("sp", Arc::new(StatefulPlugin { name: "sp" }))],
spec,
);
let resolved = resolve(®s, "a").unwrap();
assert!(
resolved.spec.sections.contains_key("stateful"),
"config section must survive even when its plugin is not active"
);
assert_eq!(resolved.spec.sections["stateful"], stateful_config());
}
#[test]
fn reactivating_plugin_picks_up_existing_config_section() {
let spec_without = AgentSpec {
plugin_ids: vec![],
..make_spec("a")
}
.with_section("stateful", stateful_config());
let regs_without = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("sp", Arc::new(StatefulPlugin { name: "sp" }))],
spec_without,
);
let resolved_without = resolve(®s_without, "a").unwrap();
assert!(resolved_without.spec.sections.contains_key("stateful"));
let spec_with = AgentSpec {
plugin_ids: vec!["sp".into()],
sections: resolved_without.spec.sections.clone(),
..make_spec("a")
};
let regs_with = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("sp", Arc::new(StatefulPlugin { name: "sp" }))],
spec_with,
);
let resolved_with = resolve(®s_with, "a").unwrap();
assert_eq!(resolved_with.spec.sections["stateful"], stateful_config());
}
#[test]
fn on_activate_reads_typed_config_from_sections() {
let config = StatefulPluginConfig {
level: "warn".into(),
max_items: 50,
};
let spec = AgentSpec {
plugin_ids: vec!["sp".into()],
..make_spec("a")
}
.with_section("stateful", serde_json::to_value(&config).unwrap());
let read_config = spec.config::<StatefulPluginConfigKey>().unwrap();
assert_eq!(read_config, config);
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("sp", Arc::new(StatefulPlugin { name: "sp" }))],
spec,
);
assert!(resolve(®s, "a").is_ok());
}
#[test]
fn on_deactivate_does_not_clear_sections() {
let plugin = StatefulPlugin { name: "sp" };
let mut patch = crate::state::MutationBatch::new();
plugin.on_deactivate(&mut patch).unwrap();
assert!(
patch.is_empty(),
"on_deactivate should not emit mutations that clear config sections"
);
}
#[test]
fn multiple_plugin_sections_survive_partial_activation() {
let spec = AgentSpec {
plugin_ids: vec!["vp".into()], ..make_spec("a")
}
.with_section(
"validated",
serde_json::json!({"mode": "strict", "threshold": 10}),
)
.with_section("stateful", stateful_config());
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![
("vp", Arc::new(ValidatedPlugin { name: "vp" })),
("sp", Arc::new(StatefulPlugin { name: "sp" })),
],
spec,
);
let resolved = resolve(®s, "a").unwrap();
assert!(resolved.spec.sections.contains_key("validated"));
assert!(
resolved.spec.sections.contains_key("stateful"),
"inactive plugin's config section must be preserved"
);
}
#[test]
fn config_defaults_when_section_absent_and_plugin_active() {
let spec = AgentSpec {
plugin_ids: vec!["sp".into()],
..make_spec("a")
};
let read_config = spec.config::<StatefulPluginConfigKey>().unwrap();
assert_eq!(read_config, StatefulPluginConfig::default());
let regs = build_registries(
vec![],
"test-model",
ModelBinding {
provider_id: "p".into(),
upstream_model: "n".into(),
},
"p",
Arc::new(MockExecutor),
vec![("sp", Arc::new(StatefulPlugin { name: "sp" }))],
spec,
);
assert!(resolve(®s, "a").is_ok());
}
}