1use std::future::Future;
22use std::pin::Pin;
23use std::sync::atomic::{AtomicU64, Ordering};
24use std::sync::{Arc, Mutex};
25use std::task::{Context, Poll};
26use std::time::Duration;
27
28use futures::StreamExt;
29use tracing::Instrument;
30
31use crate::error::ProviderError;
32use crate::observe::{AdapterContext, AdapterSlot};
33use crate::streaming::{Item, Streamed};
34use crate::wasm_compat::{WasmBoxedFuture, WasmBoxedStream, WasmCompatSend, WasmCompatSync};
35use crate::wire::document::Reassemble;
36use crate::wire::{
37 Call, Capabilities, Decoder, Descriptor, Flow, Mode, Operation, Out, Request, Response, Shared,
38 Wire, WireEvent,
39};
40
41mod dyn_model;
42pub(crate) mod http_transport;
43mod local;
44
45pub use dyn_model::DynModel;
46pub use local::{Local, Step};
47
48#[derive(Clone, Debug, PartialEq)]
55pub struct Model<W, T = crate::http_client::DynHttpClient> {
56 pub wire: W,
58 pub transport: T,
60 prepared: crate::wire::Prepared,
62 idle_timeout: Option<Duration>,
64}
65
66pub const STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(300);
72
73impl<W: Default, T: Default> Default for Model<W, T> {
74 fn default() -> Self {
75 Self::new(W::default(), T::default())
76 }
77}
78
79impl<W, T> Model<W, T> {
80 pub fn new(wire: W, transport: T) -> Self {
83 Self {
84 wire,
85 transport,
86 prepared: crate::wire::Prepared::default(),
87 idle_timeout: Some(STREAM_IDLE_TIMEOUT),
88 }
89 }
90
91 pub fn with_idle_timeout(mut self, timeout: Option<Duration>) -> Self {
97 self.idle_timeout = timeout;
98 self
99 }
100}
101
102impl<W, T> Model<W, T>
103where
104 W: Wire<Op = crate::operation::Completion>,
105 T: Transport<W>,
106{
107 pub fn check(
130 &self,
131 request: &crate::completion::CompletionRequest,
132 ) -> Result<(), crate::completion::CheckError> {
133 check_with(request, |request| self.dry_run(request))
134 }
135}
136
137pub(crate) fn check_with(
143 request: &crate::completion::CompletionRequest,
144 run: impl FnOnce(crate::completion::CompletionRequest) -> Result<(), ProviderError>,
145) -> Result<(), crate::completion::CheckError> {
146 use crate::completion::{CheckError, OnUnsupported};
147 let mut request = without_messages(request);
148 if !request.options.is_default() {
149 request.options.on_unsupported = Some(OnUnsupported::Ignore);
150 }
151 match crate::completion::options::collect_refusals(|| run(request)) {
152 (Err(error), _) => Err(CheckError::Invalid(error)),
153 (Ok(()), refusals) if refusals.is_empty() => Ok(()),
154 (Ok(()), refusals) => Err(CheckError::Unsupported(refusals)),
155 }
156}
157
158pub(crate) fn without_messages(
161 request: &crate::completion::CompletionRequest,
162) -> crate::completion::CompletionRequest {
163 use crate::completion::{CompletionRequest, Message};
164 let CompletionRequest {
165 model,
166 chat_history,
167 documents,
168 tools,
169 temperature,
170 max_tokens,
171 tool_choice,
172 additional_params,
173 output_schema,
174 record_telemetry_content,
175 accept_unknown_finish_reasons,
176 foreign_reasoning,
177 options,
178 provider_options,
179 cache_points,
180 } = request;
181 let system = chat_history
182 .first()
183 .filter(|message| matches!(&***message, Message::System { .. }));
184 CompletionRequest {
185 model: model.clone(),
186 chat_history: system
187 .cloned()
188 .into_iter()
189 .chain([std::sync::Arc::new(Message::user("check"))])
190 .collect(),
191 documents: documents.clone(),
192 tools: tools.clone(),
193 temperature: *temperature,
194 max_tokens: *max_tokens,
195 tool_choice: tool_choice.clone(),
196 additional_params: additional_params.clone(),
197 output_schema: output_schema.clone(),
198 record_telemetry_content: *record_telemetry_content,
199 accept_unknown_finish_reasons: *accept_unknown_finish_reasons,
200 foreign_reasoning: *foreign_reasoning,
201 options: options.clone(),
202 provider_options: provider_options.clone(),
203 cache_points: cache_points.clone(),
204 }
205}
206
207pub trait Transport<W: Wire>: Clone + WasmCompatSend + WasmCompatSync + 'static {
214 fn send(&self, payload: W::Payload, exchange: Exchange) -> Opening<W::Frame>;
219}
220
221#[derive(Clone)]
223pub struct Exchange {
224 pub mode: Mode,
226 pub(crate) observation: Option<AdapterContext>,
228 pub(crate) extensions: http::Extensions,
231}
232
233#[derive(Default)]
236pub(crate) struct Sending {
237 pub(crate) observation: Option<AdapterContext>,
238 pub(crate) extensions: http::Extensions,
239}
240
241impl Sending {
242 pub(crate) fn observed(observation: AdapterContext) -> Self {
244 Self {
245 observation: Some(observation),
246 extensions: http::Extensions::new(),
247 }
248 }
249}
250
251pub struct Opening<F> {
253 open: WasmBoxedFuture<'static, Result<Opened<F>, ProviderError>>,
254 slot: Option<AdapterSlot>,
257}
258
259impl<F: WasmCompatSend + 'static> Opening<F> {
260 pub fn new(
262 open: impl Future<Output = Result<Opened<F>, ProviderError>> + WasmCompatSend + 'static,
263 ) -> Self {
264 Self {
265 open: Box::pin(open),
266 slot: None,
267 }
268 }
269
270 pub(crate) fn with_slot(mut self, slot: Option<AdapterSlot>) -> Self {
272 self.slot = slot;
273 self
274 }
275
276 pub fn ready(opened: Opened<F>) -> Self {
278 Self::new(std::future::ready(Ok(opened)))
279 }
280
281 pub fn failed(error: ProviderError) -> Self {
283 Self::new(std::future::ready(Err(error)))
284 }
285}
286
287impl<F> Future for Opening<F> {
288 type Output = Result<Opened<F>, ProviderError>;
289
290 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
291 self.open.as_mut().poll(cx)
292 }
293}
294
295pub struct Opened<F> {
298 pub(crate) frames: WasmBoxedStream<'static, Result<F, ProviderError>>,
299 pub(crate) request_id: Option<String>,
300 pub(crate) status: Option<http::StatusCode>,
301 pub(crate) headers: Option<http::HeaderMap>,
302 pub(crate) route: Option<String>,
303 pub(crate) document: Option<serde_json::Value>,
304 pub(crate) slot: Option<AdapterSlot>,
305 pub(crate) analysis_only: Option<fn(&F) -> bool>,
306 pub(crate) received: Option<Arc<Received>>,
309}
310
311impl<F: WasmCompatSend + 'static> Opened<F> {
312 pub fn new(
314 frames: impl futures::Stream<Item = Result<F, ProviderError>> + WasmCompatSend + 'static,
315 ) -> Self {
316 Self {
317 frames: Box::pin(frames),
318 request_id: None,
319 status: None,
320 headers: None,
321 route: None,
322 document: None,
323 slot: None,
324 analysis_only: None,
325 received: None,
326 }
327 }
328
329 pub fn failed(error: ProviderError) -> Self {
331 Self::new(futures::stream::once(async move { Err(error) }))
332 }
333
334 pub fn with_request_id(mut self, request_id: Option<String>) -> Self {
337 self.request_id = crate::provider_response::reported(request_id);
338 self
339 }
340
341 pub fn with_http(mut self, status: http::StatusCode, headers: http::HeaderMap) -> Self {
343 self.status = Some(status);
344 self.headers = Some(headers);
345 self
346 }
347
348 pub fn with_route(mut self, route: impl Into<String>) -> Self {
350 self.route = Some(route.into());
351 self
352 }
353
354 pub fn with_document(mut self, document: serde_json::Value) -> Self {
357 self.document = Some(document);
358 self
359 }
360
361 pub fn map_frames<S>(
364 mut self,
365 frames: impl FnOnce(WasmBoxedStream<'static, Result<F, ProviderError>>) -> S,
366 ) -> Self
367 where
368 S: futures::Stream<Item = Result<F, ProviderError>> + WasmCompatSend + 'static,
369 {
370 self.frames = Box::pin(frames(self.frames));
371 self
372 }
373}
374
375impl<W, T> Model<W, T>
376where
377 W: Wire,
378 T: Transport<W>,
379{
380 pub fn name(&self) -> &str {
382 self.wire.describe().name
383 }
384
385 pub fn id(&self) -> Option<&str> {
387 self.wire.describe().model
388 }
389
390 pub fn capabilities(&self) -> Capabilities {
393 self.wire.describe().capabilities
394 }
395
396 pub fn call(
401 &self,
402 request: impl Into<Request<W>>,
403 ) -> impl Future<Output = Result<Response<W>, ProviderError>> + WasmCompatSend + 'static {
404 self.finished(request.into(), Sending::default())
405 }
406
407 pub fn call_observed(
409 &self,
410 request: impl Into<Request<W>>,
411 observation: AdapterContext,
412 ) -> impl Future<Output = Result<Response<W>, ProviderError>> + WasmCompatSend + 'static {
413 self.finished(request.into(), Sending::observed(observation))
414 }
415
416 fn finished(
419 &self,
420 request: Request<W>,
421 sending: Sending,
422 ) -> impl Future<Output = Result<Response<W>, ProviderError>> + WasmCompatSend + 'static {
423 let model = self.clone();
424 async move { model.open(request, Mode::Unary, sending)?.finish().await }
425 }
426
427 pub fn stream(&self, request: impl Into<Request<W>>) -> Result<Streamed<W::Op>, ProviderError> {
431 self.open(request.into(), Mode::Streaming, Sending::default())
432 }
433
434 pub fn stream_observed(
436 &self,
437 request: impl Into<Request<W>>,
438 observation: AdapterContext,
439 ) -> Result<Streamed<W::Op>, ProviderError> {
440 self.open(
441 request.into(),
442 Mode::Streaming,
443 Sending::observed(observation),
444 )
445 }
446
447 pub(crate) async fn call_routed(
450 &self,
451 request: Request<W>,
452 ) -> Result<Response<W>, (ProviderError, String)> {
453 self.open(request, Mode::Unary, Sending::default())
454 .map_err(|error| (error, String::new()))?
455 .finish_routed()
456 .await
457 }
458
459 pub(crate) fn dry_run(&self, request: Request<W>) -> Result<(), ProviderError> {
462 let describe = Descriptor {
463 prepared: Some(&self.prepared),
464 ..self.wire.describe()
465 };
466 let request = <W::Op as Operation>::prepare(request, &describe)?;
467 self.wire.encode(request, Mode::Unary)?;
468 Ok(())
469 }
470
471 pub(crate) fn open(
475 &self,
476 request: Request<W>,
477 mode: Mode,
478 sending: Sending,
479 ) -> Result<Streamed<W::Op>, ProviderError> {
480 let describe = Descriptor {
481 prepared: Some(&self.prepared),
482 ..self.wire.describe()
483 };
484 let request = <W::Op as Operation>::prepare(request, &describe)?;
485 let provider = describe.name.to_owned();
486 let mut call = Call::new(&describe, mode);
487 let fold = <W::Op as Operation>::fold(&request, &mut call);
488 let span = call.span;
489 let payload = self.wire.encode(request, mode)?;
490 let Sending {
491 observation,
492 extensions,
493 } = sending;
494 let exchange = Exchange {
495 mode,
496 observation,
497 extensions,
498 };
499 let opening = self.transport.send(payload, exchange);
500 let shared = Arc::new(Mutex::new(Shared::new(fold)));
501 let idle = match mode {
503 Mode::Streaming => self.idle_timeout,
504 Mode::Unary => None,
505 };
506 let reading = read(
507 self.wire.clone(),
508 opening,
509 Arc::clone(&shared),
510 span.clone(),
511 (mode, idle),
512 );
513 Ok(Streamed::new(reading, shared, span, provider))
514 }
515}
516
517fn read<W: Wire>(
528 wire: W,
529 opening: Opening<W::Frame>,
530 shared: Arc<Mutex<Shared<W::Op>>>,
531 span: tracing::Span,
532 (mode, idle): (Mode, Option<Duration>),
533) -> WasmBoxedStream<'static, ()> {
534 let decoding = span.clone();
535 let reading = async_stream::stream! {
536 let reply: &Mutex<Shared<W::Op>> = &shared;
537 let opening_slot = opening.slot.clone();
538 let opened = match mode {
540 Mode::Unary => opening.instrument(span.clone()).await,
541 Mode::Streaming => idle_within(idle, None, opening)
542 .await
543 .and_then(|opened| opened),
544 };
545 let Opened {
546 mut frames,
547 request_id,
548 status,
549 headers,
550 route,
551 document,
552 slot,
553 analysis_only,
554 received,
555 } = match opened {
556 Ok(opened) => opened,
557 Err(error) => {
558 fail(reply, opening_slot.as_ref(), error);
559 return;
560 }
561 };
562 record_request_id(&span, request_id.as_deref());
563 let mut reassembler = document.is_none().then(|| wire.reassembler());
564 {
565 let mut state = lock(reply);
566 state.request_id.clone_from(&request_id);
567 state.document = document;
568 state.route = route.unwrap_or_default();
569 }
570 let enrich = |error: ProviderError| match mode {
571 Mode::Unary => error
573 .with_provider_status(status)
574 .with_provider_request_id(request_id.clone())
575 .with_response_headers(headers.clone()),
576 Mode::Streaming => error,
577 };
578 let mut decoder = wire.decoder();
579 let mut tally = slot.as_ref().map(|slot| Tally {
580 slot,
581 analysis_only,
582 counted: 0,
583 });
584 loop {
585 let next = match idle_within(idle, received.as_deref(), frames.next()).await {
586 Ok(next) => next,
587 Err(stalled) => {
588 record(reply, reassembler.map(|document| document.finish()));
589 fail(reply, slot.as_ref(), stalled);
590 return;
591 }
592 };
593 let flow = match next {
594 Some(Ok(frame)) => step(
595 &mut decoder,
596 reassembler.as_mut(),
597 reply,
598 frame,
599 tally.as_mut(),
600 ),
601 Some(Err(error)) => {
602 record(reply, reassembler.map(|document| document.finish()));
603 fail(reply, slot.as_ref(), error);
604 return;
605 }
606 None => eof(&mut decoder, reply, tally.as_ref()),
607 };
608 match flow {
609 Ok(Flow::More) => yield (),
610 Ok(Flow::Ended(_)) => break,
611 Err(error) => {
612 record(reply, reassembler.map(|document| document.finish()));
613 fail(reply, slot.as_ref(), enrich(error));
614 return;
615 }
616 }
617 }
618 record(reply, reassembler.map(|document| document.finish()));
619 if let Some(slot) = &slot {
620 let usage = lock(reply)
621 .end
622 .as_ref()
623 .and_then(W::Op::observed_usage)
624 .cloned();
625 slot.terminal(usage.as_ref());
626 }
627 {
628 let state = lock(reply);
629 if let Some(document) = state.raw.as_ref().or(state.document.as_ref()) {
630 crate::providers::internal::trace_json(
631 crate::providers::internal::LogTarget::Completions,
632 "reply",
633 document,
634 );
635 }
636 }
637 yield ();
638 };
639 let mut reading: WasmBoxedStream<'static, ()> = Box::pin(reading);
640 match mode {
641 Mode::Streaming => Box::pin(futures::stream::poll_fn(move |cx| {
643 let _decoding = decoding.enter();
644 reading.as_mut().poll_next(cx)
645 })),
646 Mode::Unary => reading,
647 }
648}
649
650const LONGEST_TIMER: Duration = Duration::from_millis(2_147_483_647);
653
654pub(crate) struct Received {
656 opened: web_time::Instant,
657 last: AtomicU64,
659}
660
661impl Received {
662 pub(crate) fn new() -> Self {
663 Self {
664 opened: web_time::Instant::now(),
665 last: AtomicU64::new(0),
666 }
667 }
668
669 pub(crate) fn mark(&self) {
671 let millis = u64::try_from(self.opened.elapsed().as_millis()).unwrap_or(u64::MAX);
672 self.last.store(millis, Ordering::Relaxed);
673 }
674
675 fn since(&self) -> Duration {
677 let last = Duration::from_millis(self.last.load(Ordering::Relaxed));
678 self.opened.elapsed().saturating_sub(last)
679 }
680}
681
682async fn idle_within<F: Future + Unpin>(
686 idle: Option<Duration>,
687 received: Option<&Received>,
688 mut next: F,
689) -> Result<F::Output, ProviderError> {
690 let Some(idle) = idle.filter(|idle| *idle <= LONGEST_TIMER) else {
691 return Ok(next.await);
692 };
693 let mut wait = idle.saturating_sub(received.map_or(Duration::ZERO, Received::since));
696 loop {
697 if let Ok(output) = crate::wasm_compat::timeout(wait, &mut next).await {
698 return Ok(output);
699 }
700 let since = received.map_or(idle, Received::since);
701 if since >= idle {
702 return Err(ProviderError::Stalled(idle));
703 }
704 wait = idle.saturating_sub(since);
705 }
706}
707
708pub(crate) struct Decoded<Op: Operation> {
711 #[cfg(any(test, feature = "test-utils"))]
713 pub(crate) items: Vec<Result<crate::streaming::Item<Op::Event>, ProviderError>>,
714 pub(crate) outcome: Result<Op::Response, ProviderError>,
715}
716
717pub(crate) struct Tally<'a, F> {
720 slot: &'a AdapterSlot,
721 analysis_only: Option<fn(&F) -> bool>,
723 counted: usize,
724}
725
726pub(crate) fn triage<E>(event: WireEvent<E>) -> Result<Item<E>, ProviderError> {
731 match event {
732 WireEvent::Known(event) => Ok(Item::Event(event)),
733 WireEvent::Unknown { event_type, value } => {
734 warn_unmodeled(&event_type, &value);
735 Ok(Item::Unknown(value))
736 }
737 WireEvent::Corrupt(error) => Err(ProviderError::from(error)),
738 }
739}
740
741pub(crate) fn step<'id, Op, F, D, R>(
745 decoder: &mut D,
746 reassembler: Option<&mut R>,
747 reply: &'id Mutex<Shared<Op>>,
748 frame: F,
749 tally: Option<&mut Tally<'_, F>>,
750) -> Result<Flow, ProviderError>
751where
752 Op: Operation,
753 D: Decoder<'id, Op, F>,
754 R: Reassemble<D::Event>,
755{
756 let exempt = tally
757 .as_ref()
758 .and_then(|tally| tally.analysis_only)
759 .is_some_and(|analysis_only| analysis_only(&frame));
760 let classified = decoder.classify(frame);
761 if let Some(tally) = tally {
762 let corrupt = matches!(classified, WireEvent::Corrupt(_));
764 if corrupt || !exempt {
765 tally.counted += 1;
766 }
767 if corrupt {
768 tally.slot.corrupt(tally.counted);
769 }
770 }
771 match triage(classified)? {
772 Item::Event(event) => {
773 if let Some(reassembler) = reassembler {
774 reassembler.absorb(&event);
775 }
776 decoder.decode(event, Out::new(reply))
777 }
778 Item::Unknown(value) => {
779 Out::new(reply).unknown(value);
780 Ok(Flow::More)
781 }
782 }
783}
784
785fn eof<'id, Op, F, D>(
788 decoder: &mut D,
789 reply: &'id Mutex<Shared<Op>>,
790 tally: Option<&Tally<'_, F>>,
791) -> Result<Flow, ProviderError>
792where
793 Op: Operation,
794 D: Decoder<'id, Op, F>,
795{
796 if let Some(tally) = tally {
797 tally.slot.transport_eof(tally.counted);
798 }
799 let step = decoder.eof(Out::new(reply));
800 if !matches!(step, Ok(Flow::Ended(_)))
801 && let Some(tally) = tally
802 {
803 tally.slot.eof(tally.counted);
804 }
805 match step {
806 Ok(Flow::More) => Err(ProviderError::Truncated),
807 step => step,
808 }
809}
810
811pub(crate) fn feed<'id, Op, F, D, R>(
815 decoder: &mut D,
816 mut reassembler: Option<R>,
817 reply: &'id Mutex<Shared<Op>>,
818 frames: impl IntoIterator<Item = F>,
819) -> Result<(), ProviderError>
820where
821 Op: Operation,
822 D: Decoder<'id, Op, F>,
823 R: Reassemble<D::Event>,
824{
825 let fed = feed_until_end(decoder, reassembler.as_mut(), reply, frames);
826 record(reply, reassembler.map(|document| document.finish()));
827 fed
828}
829
830fn feed_until_end<'id, Op, F, D, R>(
831 decoder: &mut D,
832 mut reassembler: Option<&mut R>,
833 reply: &'id Mutex<Shared<Op>>,
834 frames: impl IntoIterator<Item = F>,
835) -> Result<(), ProviderError>
836where
837 Op: Operation,
838 D: Decoder<'id, Op, F>,
839 R: Reassemble<D::Event>,
840{
841 for frame in frames {
842 if let Flow::Ended(_) = step(decoder, reassembler.as_deref_mut(), reply, frame, None)? {
843 return Ok(());
844 }
845 }
846 eof(decoder, reply, None).map(drop)
847}
848
849pub(crate) fn record<Op: Operation>(
852 reply: &Mutex<Shared<Op>>,
853 document: Option<serde_json::Value>,
854) {
855 if let Some(document) = document.filter(|document| !document.is_null()) {
856 lock(reply).raw = Some(document);
857 }
858}
859
860pub(crate) fn settle<Op: Operation>(
864 shared: Mutex<Shared<Op>>,
865 fed: Result<(), ProviderError>,
866 reply: crate::wire::Reply,
867) -> Decoded<Op> {
868 let mut shared = shared
869 .into_inner()
870 .unwrap_or_else(std::sync::PoisonError::into_inner);
871 if let Err(error) = fed {
872 shared.items.push_back(Err(error));
873 }
874 shared.document = Some(reply.raw).filter(|raw| !raw.is_null());
875 shared.request_id = reply.provider_request_id;
876 let mut items = Vec::new();
877 while let Some(item) = shared.take() {
878 let failed = item.is_err();
879 items.push(item);
880 if failed {
881 break;
882 }
883 }
884 let outcome = match items.last() {
885 Some(Err(error)) => Err(error.clone()),
886 _ => shared.conclude(&reply.provider),
887 };
888 #[cfg(any(test, feature = "test-utils"))]
889 if let Err(error) = &outcome
890 && !matches!(items.last(), Some(Err(_)))
891 {
892 items.push(Err(error.clone()));
893 }
894 Decoded {
895 #[cfg(any(test, feature = "test-utils"))]
896 items,
897 outcome,
898 }
899}
900
901pub(crate) fn decode_reply<W: Wire>(
905 wire: &W,
906 request: &Request<W>,
907 mode: Mode,
908 frames: impl IntoIterator<Item = W::Frame>,
909 raw: serde_json::Value,
910) -> Result<Response<W>, ProviderError> {
911 let describe = wire.describe();
912 let fold = <W::Op as Operation>::fold(request, &mut Call::new(&describe, mode));
913 let shared = Mutex::new(Shared::new(fold));
914 let fed = feed(
915 &mut wire.decoder(),
916 Some(wire.reassembler()),
917 &shared,
918 frames,
919 );
920 let reply = crate::wire::Reply {
921 provider: describe.name.to_owned(),
922 raw,
923 provider_request_id: None,
924 };
925 settle(shared, fed, reply).outcome
926}
927
928#[cfg(any(test, feature = "test-utils"))]
931pub(crate) fn relay_frames<W>(
932 wire: &W,
933 frames: impl IntoIterator<Item = W::Frame>,
934) -> crate::streaming::StreamEvents
935where
936 W: Wire<Op = crate::operation::Completion>,
937{
938 use crate::error::ErrorReport;
939 use crate::streaming::Relayed;
940
941 let provider = wire.describe().name.to_owned();
942 let shared = Mutex::new(Shared::new(crate::operation::ReplyFold::relayed(
943 provider.clone(),
944 )));
945 let fed = feed(
946 &mut wire.decoder(),
947 Some(wire.reassembler()),
948 &shared,
949 frames,
950 );
951 let decoded = settle(
952 shared,
953 fed,
954 crate::wire::Reply {
955 provider,
956 raw: serde_json::Value::Null,
957 provider_request_id: None,
958 },
959 );
960 let origin = decoded
961 .outcome
962 .as_ref()
963 .map(|response| response.origin.clone())
964 .ok();
965 let relayed: Vec<Result<Relayed, ErrorReport>> = origin
966 .map(|origin| Ok(Relayed::Origin(origin)))
967 .into_iter()
968 .chain(decoded.items.into_iter().map(|item| match item {
969 Ok(item) => Ok(Relayed::Item(item)),
970 Err(error) => Err(ErrorReport::from(&error)),
971 }))
972 .chain(
974 decoded
975 .outcome
976 .ok()
977 .map(|response| Ok(Relayed::Done(Arc::new(response)))),
978 )
979 .collect();
980 Box::pin(futures::stream::iter(relayed))
981}
982
983#[cfg(test)]
984impl Decoded<crate::operation::Completion> {
985 pub(crate) fn events(&self) -> Vec<&crate::streaming::StreamEvent> {
987 self.items
988 .iter()
989 .filter_map(|item| match item {
990 Ok(crate::streaming::Item::Event(event)) => Some(event),
991 _ => None,
992 })
993 .collect()
994 }
995
996 pub(crate) fn ended(&self) -> Vec<crate::message::AssistantContent> {
998 self.events()
999 .into_iter()
1000 .filter_map(|event| match event {
1001 crate::streaming::StreamEvent::End { content, .. } => Some(content.clone()),
1002 _ => None,
1003 })
1004 .collect()
1005 }
1006}
1007
1008#[cfg(test)]
1011macro_rules! decode_events {
1012 ($decoder:expr, $provider:expr, $events:expr) => {
1013 $crate::driver::decode_with(
1014 $crate::operation::ReplyFold::relayed($provider),
1015 $provider,
1016 |reply| {
1017 let mut decoder = $decoder;
1018 for event in $events {
1019 if let $crate::wire::Flow::Ended(_) =
1020 $crate::wire::Decoder::decode(&mut decoder, event, reply.out())?
1021 {
1022 return Ok(());
1023 }
1024 }
1025 match $crate::wire::Decoder::eof(&mut decoder, reply.out())? {
1026 $crate::wire::Flow::Ended(_) => Ok(()),
1027 $crate::wire::Flow::More => Err($crate::error::ProviderError::Truncated),
1028 }
1029 },
1030 )
1031 };
1032}
1033#[cfg(test)]
1034pub(crate) use decode_events;
1035
1036#[cfg(test)]
1041macro_rules! feed_frames {
1042 ($decoder:expr, $provider:expr, $frames:expr) => {
1043 $crate::driver::decode_with(
1044 $crate::operation::ReplyFold::relayed($provider),
1045 $provider,
1046 |reply| {
1047 let mut decoder = $decoder;
1048 reply.feed(
1049 &mut decoder,
1050 None::<$crate::wire::document::Unreassembled>,
1051 $frames,
1052 )
1053 },
1054 )
1055 };
1056 ($decoder:expr, $reassembler:expr, $provider:expr, $frames:expr) => {
1057 $crate::driver::decode_with(
1058 $crate::operation::ReplyFold::relayed($provider),
1059 $provider,
1060 |reply| {
1061 let mut decoder = $decoder;
1062 reply.feed(&mut decoder, Some($reassembler), $frames)
1063 },
1064 )
1065 };
1066}
1067#[cfg(test)]
1068pub(crate) use feed_frames;
1069
1070#[cfg(test)]
1072pub(crate) struct Replying<'id, Op: Operation>(&'id Mutex<Shared<Op>>);
1073
1074#[cfg(test)]
1075impl<'id, Op: Operation> Replying<'id, Op> {
1076 pub(crate) fn out(&self) -> Out<'id, Op> {
1078 Out::new(self.0)
1079 }
1080
1081 pub(crate) fn feed<F, D: Decoder<'id, Op, F>, R: Reassemble<D::Event>>(
1083 &self,
1084 decoder: &mut D,
1085 reassembler: Option<R>,
1086 frames: impl IntoIterator<Item = F>,
1087 ) -> Result<(), ProviderError> {
1088 feed(decoder, reassembler, self.0, frames)
1089 }
1090}
1091
1092#[cfg(test)]
1096pub(crate) fn decode_with<Op: Operation>(
1097 fold: Op::Fold,
1098 provider: &str,
1099 run: impl for<'id> FnOnce(Replying<'id, Op>) -> Result<(), ProviderError>,
1100) -> Decoded<Op> {
1101 let shared = Mutex::new(Shared::new(fold));
1102 let fed = run(Replying(&shared));
1103 settle(
1104 shared,
1105 fed,
1106 crate::wire::Reply {
1107 provider: provider.to_owned(),
1108 raw: serde_json::Value::Null,
1109 provider_request_id: None,
1110 },
1111 )
1112}
1113
1114fn fail<Op: Operation>(
1116 reply: &Mutex<Shared<Op>>,
1117 slot: Option<&AdapterSlot>,
1118 error: ProviderError,
1119) {
1120 if let Some(slot) = slot {
1121 slot.fail(&error);
1122 }
1123 lock(reply).items.push_back(Err(error));
1124}
1125
1126pub(crate) fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
1127 mutex
1128 .lock()
1129 .unwrap_or_else(std::sync::PoisonError::into_inner)
1130}
1131
1132pub(crate) async fn follow_cursors<P, Fut>(
1136 provider: &str,
1137 operation: &str,
1138 mut page: impl FnMut(Option<String>) -> Fut,
1139) -> Result<Vec<P>, ProviderError>
1140where
1141 Fut: Future<Output = Result<(P, Option<String>), ProviderError>>,
1142{
1143 let mut pages = Vec::new();
1144 let mut cursor = None;
1145 loop {
1146 let (read, next) = page(cursor.clone()).await?;
1147 pages.push(read);
1148 let Some(next) = next else { break };
1151 if cursor.as_deref() == Some(next.as_str()) {
1152 tracing::warn!(
1155 provider,
1156 operation,
1157 pages = pages.len(),
1158 "listing repeated its pagination cursor; returning the pages fetched so far"
1159 );
1160 break;
1161 }
1162 if pages.len() >= MAX_CONTINUATION_PAGES {
1163 tracing::warn!(
1164 provider,
1165 operation,
1166 pages = pages.len(),
1167 "listing hit its page ceiling with a cursor still advancing; returning the pages \
1168 fetched so far"
1169 );
1170 break;
1171 }
1172 cursor = Some(next);
1173 }
1174 Ok(pages)
1175}
1176
1177pub fn warn_unmodeled(kind: &str, payload: &impl serde::Serialize) {
1180 tracing::warn!(
1181 kind,
1182 payload_bytes = unknown_payload_bytes(payload),
1183 "skipping unmodeled wire payload"
1184 );
1185}
1186
1187fn unknown_payload_bytes(value: &impl serde::Serialize) -> u64 {
1190 struct CountingWriter(u64);
1193
1194 impl std::io::Write for CountingWriter {
1195 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1196 self.0 += buf.len() as u64;
1197 Ok(buf.len())
1198 }
1199
1200 fn flush(&mut self) -> std::io::Result<()> {
1201 Ok(())
1202 }
1203 }
1204
1205 let mut counter = CountingWriter(0);
1206 let _ = serde_json::to_writer(&mut counter, value);
1208 counter.0
1209}
1210
1211const MAX_CONTINUATION_PAGES: usize = 1000;
1214
1215pub(crate) fn record_request_id(span: &tracing::Span, request_id: Option<&str>) {
1217 if let Some(request_id) = request_id
1218 && !span.is_disabled()
1219 {
1220 span.record(crate::telemetry::PROVIDER_REQUEST_ID_FIELD, request_id);
1221 }
1222}
1223
1224#[cfg(test)]
1225pub(crate) mod tests;