Skip to main content

cc_lb_plugin_api/
traits.rs

1//! Object-safe plugin traits for each proxy lifecycle boundary.
2
3use std::sync::Arc;
4
5use async_trait::async_trait;
6use bytes::Bytes;
7use http::StatusCode;
8use uuid::Uuid;
9
10use crate::errors::{
11    DialectError, ObservabilityError, RouteError, RuntimeError, SignerError, UpstreamError,
12};
13use crate::types::{
14    ObserveEvent, PerCandidateReason, PluginManifest, Principal, RequestContext, RetryDecision,
15    RouteDecision, ShapedRequest, ShapedRequestBuilder, SignedRequest, SigningCapability, Upstream,
16    UpstreamCandidate,
17};
18
19/// Filter plugin output containing upstream selection results and per-candidate reasons.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct FilterOutput {
22    /// Upstream IDs that passed the filter.
23    pub kept_upstream_ids: Vec<Uuid>,
24    /// Human-readable reason for the filtering decision.
25    pub reason: String,
26    /// Per-candidate filtering reasons.
27    pub per_candidate_reasons: Vec<PerCandidateReason>,
28}
29
30/// Filter plugin errors returned by [`FilterPlugin`].
31#[derive(Debug)]
32pub enum FilterError {
33    /// Runtime error during filtering.
34    Runtime {
35        /// Redacted runtime error reason.
36        reason: String,
37    },
38    /// Trap error (plugin crashed or returned invalid state).
39    Trap {
40        /// Redacted trap error reason.
41        reason: String,
42    },
43}
44
45impl std::fmt::Display for FilterError {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        match self {
48            FilterError::Runtime { reason } => write!(f, "filter runtime error: {}", reason),
49            FilterError::Trap { reason } => write!(f, "filter trap error: {}", reason),
50        }
51    }
52}
53
54impl std::error::Error for FilterError {}
55
56/// Filter plugin boundary for upstream candidate filtering and decision-making.
57///
58/// Filter plugins evaluate requests against custom criteria and decide which upstream
59/// candidates are acceptable. They have access to the request context, authenticated
60/// principal, and list of available upstream candidates, and return a filtered set
61/// of acceptable upstreams along with per-candidate reasoning.
62pub trait FilterPlugin: Send + Sync {
63    /// Filters upstream candidates based on request and principal.
64    ///
65    /// Returns a [`FilterOutput`] containing the kept upstream IDs and per-candidate
66    /// reasons, or a [`FilterError`] if filtering fails.
67    fn filter(
68        &self,
69        ctx: &RequestContext,
70        principal: &Principal,
71        candidates: &[UpstreamCandidate],
72    ) -> Result<FilterOutput, FilterError>;
73
74    /// Returns the stable plugin identifier.
75    fn plugin_id(&self) -> Uuid;
76
77    /// Returns the human-readable plugin name.
78    fn plugin_name(&self) -> &str;
79}
80
81/// Router plugin boundary.
82#[deprecated(since = "0.2.0", note = "Use FilterPlugin via wire v3")]
83pub trait RouterPlugin: Send + Sync {
84    /// Selects the upstream and dialect for an authenticated request.
85    ///
86    /// The `candidates` parameter provides the list of available upstreams that can be
87    /// selected. Candidates are sorted by `upstream_id` ascending (Uuid byte order) to enable
88    /// deterministic routing algorithms.
89    fn route(
90        &self,
91        ctx: &RequestContext,
92        principal: &Principal,
93        candidates: &[UpstreamCandidate],
94    ) -> Result<RouteDecision, RouteError>;
95}
96
97/// Upstream dialect boundary for request shaping and error normalization.
98pub trait UpstreamDialect: Send + Sync {
99    /// Shapes a downstream Anthropic-compatible request for the selected upstream.
100    fn shape(
101        &self,
102        ctx: &RequestContext,
103        upstream: &Upstream,
104        principal: &Principal,
105        builder: &mut ShapedRequestBuilder,
106    ) -> Result<ShapedRequest, DialectError>;
107
108    /// Normalizes an upstream error body to Anthropic error shape when possible.
109    fn normalize_error(&self, status: StatusCode, body: &Bytes) -> Option<Bytes>;
110}
111
112/// Signer boundary for applying credentials to shaped requests.
113#[async_trait]
114pub trait Signer: Send + Sync {
115    /// Consumes a shaped request and returns a sealed signed request.
116    async fn sign(
117        &self,
118        shaped: ShapedRequest,
119        capability: &mut SigningCapability,
120    ) -> Result<SignedRequest, SignerError>;
121
122    /// Handles an unauthorized upstream response, optionally refreshing credentials.
123    async fn on_unauthorized(&self, err: &UpstreamError) -> RetryDecision;
124}
125
126/// Factory that builds upstream-specific signers.
127#[async_trait]
128pub trait SignerFactory: Send + Sync {
129    /// Builds a signer for the selected upstream.
130    async fn build(&self, upstream: &Upstream) -> Result<Arc<dyn Signer>, SignerError>;
131}
132
133/// Factory extension that binds signer construction to the router-selected upstream.
134pub trait ApiKeyAwareSignerFactory: Send + Sync {
135    /// Returns a signer factory using the downstream API key and router-selected upstream name.
136    fn with_router_choice(
137        &self,
138        api_key: String,
139        router_chosen_upstream_name: String,
140    ) -> Arc<dyn SignerFactory>;
141}
142
143/// Non-blocking observability hook boundary.
144pub trait ObservabilityHook: Send + Sync {
145    /// Observes a lifecycle event.
146    fn observe(&self, event: ObserveEvent) -> Result<(), ObservabilityError>;
147}
148
149/// Runtime abstraction for concrete plugin systems such as Extism.
150pub trait PluginRuntime: Send + Sync {
151    /// Instantiates a router plugin.
152    #[allow(deprecated)]
153    fn instantiate_router(
154        &self,
155        manifest: &PluginManifest,
156    ) -> Result<Arc<dyn RouterPlugin>, RuntimeError>;
157
158    /// Instantiates an upstream dialect plugin.
159    fn instantiate_dialect(
160        &self,
161        manifest: &PluginManifest,
162    ) -> Result<Arc<dyn UpstreamDialect>, RuntimeError>;
163
164    /// Instantiates a signer factory plugin.
165    fn instantiate_signer_factory(
166        &self,
167        manifest: &PluginManifest,
168    ) -> Result<Arc<dyn SignerFactory>, RuntimeError>;
169
170    /// Instantiates an observability hook plugin.
171    fn instantiate_observability(
172        &self,
173        manifest: &PluginManifest,
174    ) -> Result<Arc<dyn ObservabilityHook>, RuntimeError>;
175}