#![allow(clippy::unused_async, dead_code)]
use std::sync::Arc;
use entelix::Error;
use entelix::ir::{ModelResponse, Usage};
use entelix::service::{
BoxedModelService, BoxedStreamingService, BoxedToolService, ModelInvocation, ModelStream,
StreamingModelInvocation, ToolInvocation,
};
use entelix::tools::{ScopedToolLayer, ToolDispatchScope};
use entelix::{
AlwaysApprove, ApprovalLayer, DroppingSink, NamedLayer, ToolEventLayer, ToolHookLayer,
ToolHookRegistry,
};
use entelix_core::ExecutionContext;
use entelix_core::transports::{RetryLayer, RetryPolicy};
use entelix_policy::{PolicyLayer, PolicyRegistry};
use futures::future::BoxFuture;
use serde_json::Value;
use tower::{Layer, Service};
fn assert_chat_model_layer<L>(_: L)
where
L: Layer<BoxedModelService>
+ Layer<BoxedStreamingService>
+ NamedLayer
+ Clone
+ Send
+ Sync
+ 'static,
<L as Layer<BoxedModelService>>::Service:
Service<ModelInvocation, Response = ModelResponse, Error = Error> + Clone + Send + 'static,
<<L as Layer<BoxedModelService>>::Service as Service<ModelInvocation>>::Future: Send + 'static,
<L as Layer<BoxedStreamingService>>::Service: Service<StreamingModelInvocation, Response = ModelStream, Error = Error>
+ Clone
+ Send
+ 'static,
<<L as Layer<BoxedStreamingService>>::Service as Service<StreamingModelInvocation>>::Future:
Send + 'static,
{
}
fn assert_tool_registry_layer<L>(_: L)
where
L: Layer<BoxedToolService> + NamedLayer + Clone + Send + Sync + 'static,
L::Service: Service<ToolInvocation, Response = Value, Error = Error> + Clone + Send + 'static,
<L::Service as Service<ToolInvocation>>::Future: Send + 'static,
{
}
fn assert_chat_model_layer_named<L>(_: L)
where
L: Layer<BoxedModelService> + Layer<BoxedStreamingService> + Clone + Send + Sync + 'static,
<L as Layer<BoxedModelService>>::Service:
Service<ModelInvocation, Response = ModelResponse, Error = Error> + Clone + Send + 'static,
<<L as Layer<BoxedModelService>>::Service as Service<ModelInvocation>>::Future: Send + 'static,
<L as Layer<BoxedStreamingService>>::Service: Service<StreamingModelInvocation, Response = ModelStream, Error = Error>
+ Clone
+ Send
+ 'static,
<<L as Layer<BoxedStreamingService>>::Service as Service<StreamingModelInvocation>>::Future:
Send + 'static,
{
}
fn assert_tool_registry_layer_named<L>(_: L)
where
L: Layer<BoxedToolService> + Clone + Send + Sync + 'static,
L::Service: Service<ToolInvocation, Response = Value, Error = Error> + Clone + Send + 'static,
<L::Service as Service<ToolInvocation>>::Future: Send + 'static,
{
}
#[derive(Clone)]
struct AnonymousPassThroughLayer;
impl<S> Layer<S> for AnonymousPassThroughLayer {
type Service = S;
fn layer(&self, inner: S) -> Self::Service {
inner
}
}
struct NoOpScope;
impl ToolDispatchScope for NoOpScope {
fn wrap(
&self,
_ctx: ExecutionContext,
fut: BoxFuture<'static, entelix::Result<Value>>,
) -> BoxFuture<'static, entelix::Result<Value>> {
fut
}
}
#[test]
fn first_party_model_spine_layers_compose_via_chat_model_layer() {
let registry = Arc::new(PolicyRegistry::new());
assert_chat_model_layer(PolicyLayer::new(registry));
assert_chat_model_layer(RetryLayer::new(RetryPolicy::standard()));
#[cfg(feature = "otel")]
assert_chat_model_layer(entelix::OtelLayer::new("test"));
}
#[test]
fn first_party_tool_spine_layers_compose_via_tool_registry_layer() {
let registry = Arc::new(PolicyRegistry::new());
assert_tool_registry_layer(PolicyLayer::new(Arc::clone(®istry)));
assert_tool_registry_layer(RetryLayer::new(RetryPolicy::standard()));
#[cfg(feature = "otel")]
assert_tool_registry_layer(entelix::OtelLayer::new("test"));
assert_tool_registry_layer(entelix_core::tools::RetryToolLayer::new());
assert_tool_registry_layer(ApprovalLayer::new(Arc::new(AlwaysApprove)));
assert_tool_registry_layer(ToolHookLayer::new(ToolHookRegistry::default()));
assert_tool_registry_layer(ScopedToolLayer::new(NoOpScope));
let sink: Arc<dyn entelix::AgentEventSink<i32>> = Arc::new(DroppingSink);
assert_tool_registry_layer(ToolEventLayer::<i32>::new(sink));
let _usage = Usage::new(0, 0);
}
#[test]
fn anonymous_tower_layer_composes_through_layer_named_helper() {
assert_chat_model_layer_named(AnonymousPassThroughLayer);
assert_tool_registry_layer_named(AnonymousPassThroughLayer);
}
#[test]
fn first_party_layer_names_are_canonical_role_buckets() {
assert_eq!(PolicyLayer::NAME, "policy");
assert_eq!(RetryLayer::NAME, "retry");
#[cfg(feature = "otel")]
assert_eq!(entelix::OtelLayer::NAME, "otel");
assert_eq!(entelix_core::tools::RetryToolLayer::NAME, "tool_retry");
assert_eq!(ApprovalLayer::NAME, "tool_approval");
assert_eq!(ToolEventLayer::<i32>::NAME, "tool_event");
assert_eq!(ToolHookLayer::NAME, "tool_hook");
assert_eq!(ScopedToolLayer::NAME, "tool_scope");
}