1use std::sync::{Arc, Mutex};
13
14use futures::StreamExt;
15use serde::{Deserialize, Serialize};
16
17use crate::{
18 effect::{Answered, EffectId, EffectKind, HandlerDescriptor, family},
19 error::{ErrorKind, ErrorReport},
20 wasm_compat::{WasmCompatSend, WasmCompatSync},
21};
22
23use super::{Dispatch, ErasedHandler, Reply, Serve, stream_truncated};
24use crate::streaming::Relayed;
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(tag = "decision", rename_all = "snake_case")]
29pub enum BeforeServe {
30 Proceed,
32 Patch(EffectKind),
36 Deny(ErrorReport),
39}
40
41impl BeforeServe {
42 pub fn deny(reason: impl Into<String>) -> Self {
44 Self::Deny(ErrorReport::new(ErrorKind::Denied, reason).with_retryable(false))
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(tag = "verdict", rename_all = "snake_case")]
51pub enum AfterServe {
52 Keep,
54 Replace(Result<Answered, ErrorReport>),
63}
64
65pub trait Intercept: WasmCompatSend + WasmCompatSync + 'static {
70 fn name(&self) -> String;
72
73 fn before(
75 &self,
76 id: EffectId,
77 kind: &EffectKind,
78 ) -> impl Future<Output = BeforeServe> + WasmCompatSend;
79
80 fn after(
84 &self,
85 id: EffectId,
86 kind: &EffectKind,
87 outcome: &Result<Answered, ErrorReport>,
88 ) -> impl Future<Output = AfterServe> + WasmCompatSend;
89}
90
91pub struct Layer<I: Intercept> {
95 inner: ErasedHandler,
96 intercept: Arc<I>,
97}
98
99impl<I: Intercept> Layer<I> {
100 pub(crate) fn new(inner: ErasedHandler, intercept: I) -> Self {
102 Self {
103 inner,
104 intercept: Arc::new(intercept),
105 }
106 }
107
108 fn internal(&self, message: String) -> ErrorReport {
109 ErrorReport::new(
110 ErrorKind::Internal,
111 format!("layer `{}`: {message}", self.intercept.name()),
112 )
113 .with_retryable(false)
114 }
115}
116
117impl<I: Intercept> Serve for Layer<I> {
118 type Family = family::Dynamic;
119
120 fn descriptor(&self) -> HandlerDescriptor {
121 let mut descriptor = self.inner.descriptor();
122 descriptor.layers.insert(0, self.intercept.name());
123 descriptor
124 }
125
126 fn key(&self) -> crate::effect::HandlerKey {
127 self.inner.key().clone()
128 }
129
130 fn footprint(&self, kind: &EffectKind) -> Option<crate::tool::Touches> {
131 self.inner.footprint(kind)
132 }
133
134 fn check(&self, kind: &EffectKind) -> Vec<crate::completion::UnsupportedOption> {
135 self.inner.check(kind)
136 }
137
138 async fn serve(&self, kind: EffectKind, mut dispatch: Dispatch) -> Reply {
139 let id = dispatch.id();
140 let name = self.intercept.name();
141 let kind = match self.intercept.before(id, &kind).await {
142 BeforeServe::Proceed => kind,
143 BeforeServe::Patch(patched) => {
144 if patched.family() != kind.family() {
145 dispatch.discard(&name);
146 return Reply::Outcome(Err(self.internal(format!(
147 "patched a {} effect into a {} effect; a layer never changes the family",
148 kind.family(),
149 patched.family()
150 ))));
151 }
152 if let (
153 EffectKind::ToolCall { name: original, .. },
154 EffectKind::ToolCall {
155 name: replacement, ..
156 },
157 ) = (&kind, &patched)
158 && original != replacement
159 {
160 dispatch.discard(&name);
161 return Reply::Outcome(Err(self.internal(format!(
162 "patched tool target `{original}` into `{replacement}`; a layer never changes the bound tool"
163 ))));
164 }
165 dispatch.patched(&patched);
166 patched
167 }
168 BeforeServe::Deny(report) => {
169 dispatch.discard(&name);
170 return Reply::Outcome(Err(report));
171 }
172 };
173 if !dispatch.is_stream() {
174 let folded = Arc::new(Mutex::new(None));
175 let inner = dispatch.inner(Some(folded.clone()));
176 let attribution = dispatch.attribution();
177 let outcome = self
178 .inner
179 .handle(kind.clone(), inner)
180 .await
181 .folded_outcome(Some(folded))
182 .await;
183 return Reply::Outcome(match self.intercept.after(id, &kind, &outcome).await {
184 AfterServe::Keep => outcome,
185 AfterServe::Replace(replacement) => {
186 attribution.replaced(&name);
187 replacement
188 }
189 });
190 }
191 let folded = Arc::new(Mutex::new(None));
192 let inner = dispatch.inner(Some(folded.clone()));
193 let attribution = dispatch.attribution();
194 let stream = self
195 .inner
196 .handle(kind.clone(), inner)
197 .await
198 .into_stream()
199 .fuse();
200 let intercept = self.intercept.clone();
201 Reply::Stream(Box::pin(futures::stream::unfold(
202 (stream, intercept, kind, folded, false, attribution),
203 move |(mut stream, intercept, kind, folded, mut decided, attribution)| async move {
204 let item = stream.next().await;
205 if decided && item.is_none() {
206 return None;
207 }
208 let outcome = if decided {
209 None
210 } else {
211 folded
212 .lock()
213 .unwrap_or_else(std::sync::PoisonError::into_inner)
214 .take()
215 };
216 let item = if let Some(outcome) = outcome {
217 decided = true;
218 match intercept.after(id, &kind, &outcome).await {
219 AfterServe::Keep => item.unwrap_or_else(|| Err(stream_truncated())),
220 AfterServe::Replace(Err(report)) => {
221 attribution.replaced(&intercept.name());
222 Err(report)
223 }
224 AfterServe::Replace(Ok(Answered::Completion(response))) => {
225 attribution.replaced(&intercept.name());
226 Ok(Relayed::Done(response))
227 }
228 AfterServe::Replace(Ok(other)) => {
229 attribution.replaced(&intercept.name());
230 Err(ErrorReport::new(
231 ErrorKind::Internal,
232 format!(
233 "layer `{}`: replaced a streamed completion with a {} outcome",
234 intercept.name(),
235 other.family()
236 ),
237 )
238 .with_retryable(false))
239 }
240 }
241 } else {
242 item?
243 };
244 Some((
245 item,
246 (stream, intercept, kind, folded, decided, attribution),
247 ))
248 },
249 )))
250 }
251}
252
253#[cfg(test)]
254mod tests;