Skip to main content

rig_core/
wire.rs

1//! Operations, provider wires and their decoders. An [`Operation`] names what
2//! goes in, what comes out event by event and what ends a reply, and its
3//! [`Fold`] turns one reply's events and end into the response. A [`Wire`]
4//! describes a provider endpoint as plain data, encodes requests without
5//! transport access, and names a fresh [`Decoder`] for each reply. A decoder
6//! is told nothing about how the reply arrives, so buffered and streamed
7//! replies go through the same decoder and fold.
8//!
9//! ```
10//! use rig_core::wire::WireFrame;
11//!
12//! let frame = WireFrame::Bytes(b"response".to_vec());
13//! assert_eq!(frame.as_str(), "response");
14//! ```
15
16use std::sync::{Arc, Mutex};
17
18use crate::error::{EncodeError, ProviderError};
19use crate::http_client::MultipartForm;
20use crate::wasm_compat::{WasmCompatSend, WasmCompatSync};
21
22pub use crate::http_client::framing::{Framing, WireFrame};
23pub use crate::observe::{
24    AdapterErrorEnvelope, AdapterEvent, AdapterUsage, AdapterVerdict, ObservationSink,
25};
26
27mod citation;
28pub mod document;
29pub(crate) mod secret;
30
31pub use citation::{SpanUnit, WireCitation, WireSpan};
32pub use secret::Secret;
33
34/// The request a wire sends, and how its reply is framed.
35///
36/// Data only: built by [`Wire::encode`] from the wire and the request, and
37/// read by the driver. A wire never touches a socket or a request extension.
38///
39/// `Debug` shows request methods and URI paths, not schemes, authorities,
40/// queries, header values or bodies. Paths are not scrubbed: callers must
41/// still avoid placing sensitive data in them.
42pub struct Encoded {
43    /// The HTTP request.
44    pub request: http::Request<Body>,
45    /// How the reply's bytes split into frames.
46    pub framing: Framing,
47    /// The reply header carrying the provider's transport request id
48    /// (Anthropic `request-id`, OpenAI `x-request-id`), when the provider
49    /// reports one. `None` is "does not report one", never an error.
50    pub request_id_header: Option<&'static str>,
51    /// Whether a streamed reply may omit `Content-Type` (one gateway
52    /// replays Responses bodies without it). A *wrong* content type is
53    /// still rejected.
54    pub relaxed_content_type: bool,
55    /// Stable endpoint template for observation grouping, without base-URL
56    /// prefixes or interpolated values. `None` uses the concrete request path.
57    pub route: Option<&'static str>,
58    /// Reads observation facts off each raw reply payload before
59    /// normalization discards them. `None` projects nothing.
60    pub project: Option<Projector>,
61    /// Whether a frame carries only analysis metadata: it still decodes, but
62    /// does not advance observation's EOF and corruption positions.
63    pub analysis_only: Option<fn(&WireFrame) -> bool>,
64}
65
66/// Reads one raw payload's observation facts (verdicts, usage, ids, error
67/// envelopes) through the sink. Text it forwards goes through
68/// [`ObservationSink::scrub`]: a payload can echo credentials.
69pub type Projector = fn(&[u8], &mut ObservationSink<'_>);
70
71impl Encoded {
72    /// One request whose provider reports no transport request id.
73    pub fn new(request: http::Request<Body>, framing: Framing) -> Self {
74        Self {
75            request,
76            framing,
77            request_id_header: None,
78            relaxed_content_type: false,
79            route: None,
80            project: None,
81            analysis_only: None,
82        }
83    }
84
85    /// Name the reply header carrying the provider's transport request id.
86    pub fn with_request_id_header(mut self, header: Option<&'static str>) -> Self {
87        self.request_id_header = header;
88        self
89    }
90
91    /// Accept a streamed reply that names no content type.
92    pub fn with_relaxed_content_type(mut self) -> Self {
93        self.relaxed_content_type = true;
94        self
95    }
96
97    /// Name the endpoint template observation groups attempts under.
98    pub fn with_route(mut self, route: Option<&'static str>) -> Self {
99        self.route = route;
100        self
101    }
102
103    /// Read observation facts off each reply payload through `project`.
104    pub fn with_projection(mut self, project: Projector) -> Self {
105        self.project = Some(project);
106        self
107    }
108
109    /// Exempt frames `analysis_only` accepts from observation's frame
110    /// positions.
111    pub fn with_analysis_only(mut self, analysis_only: fn(&WireFrame) -> bool) -> Self {
112        self.analysis_only = Some(analysis_only);
113        self
114    }
115}
116
117/// A request body: bytes, or a multipart form for the upload endpoints.
118///
119/// `Debug` prints the body's shape and size, never its bytes: a request
120/// body carries prompts, documents and uploaded files.
121pub enum Body {
122    /// A serialized body (JSON for every wire in this crate, or empty).
123    Bytes(Vec<u8>),
124    /// A multipart form (audio transcription, image edits).
125    Multipart(MultipartForm),
126}
127
128impl Body {
129    /// An empty body, for a `GET`.
130    pub fn empty() -> Self {
131        Self::Bytes(Vec::new())
132    }
133}
134
135impl std::fmt::Debug for Body {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        match self {
138            Self::Bytes(bytes) => write!(f, "Bytes({} bytes)", bytes.len()),
139            Self::Multipart(_) => f.write_str("Multipart"),
140        }
141    }
142}
143
144impl std::fmt::Debug for Encoded {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        // Diagnostics omit query, authority, headers and bodies. A caller's
147        // path can still contain sensitive data; it is not scrubbed here.
148        f.debug_struct("Encoded")
149            .field(
150                "request",
151                &(self.request.method(), self.request.uri().path()),
152            )
153            .field("framing", &self.framing)
154            .field("request_id_header", &self.request_id_header)
155            .field("relaxed_content_type", &self.relaxed_content_type)
156            .field("route", &self.route)
157            .field("project", &self.project.is_some())
158            .finish()
159    }
160}
161
162/// Reply mode used by the wire to select request encoding and framing.
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum Mode {
165    /// One whole reply ([`Model::call`](crate::driver::Model::call)).
166    Unary,
167    /// A streamed reply ([`Model::stream`](crate::driver::Model::stream)).
168    Streaming,
169}
170
171/// An operation: what goes in, what comes out event by event, what ends a
172/// reply, and how those fold into one response.
173///
174/// Implemented once per operation, never per provider, so every provider of
175/// an operation is interchangeable behind a [`DynModel`](crate::DynModel).
176pub trait Operation: Sized + 'static {
177    /// The normalized request this operation accepts.
178    type Request: WasmCompatSend + 'static;
179    /// One decoded step of a reply.
180    type Event: WasmCompatSend + 'static;
181    /// What the provider sends when it ends the reply. A fold needs one to
182    /// produce a response, so a reply that stopped early has none.
183    type End: WasmCompatSend + 'static;
184    /// The normalized response the events and the end fold into.
185    type Response: WasmCompatSend + 'static;
186    /// One reply's state: it absorbs the events the consumer sees, then
187    /// folds them with the end into the response.
188    type Fold: Fold<Self> + WasmCompatSend + 'static;
189    /// Who builds the events: [`Free`] for events a decoder writes itself,
190    /// [`Assembled`] for the completion events only the writer's part
191    /// handles build.
192    type Emit: Emit<Self>;
193
194    /// The state for one reply to `request`, before its first frame. The
195    /// call says who answers and in which mode; an operation that records
196    /// telemetry opens its span here and hands it to
197    /// [`Call::instrument`].
198    fn fold(request: &Self::Request, call: &mut Call<'_>) -> Self::Fold;
199
200    /// Shape `request` for the wire `wire` describes, or reject a request
201    /// no provider of this operation can answer. The driver calls it before
202    /// the request is encoded, so a rejected request reaches no wire or
203    /// transport. Passes every request through by default.
204    fn prepare(
205        request: Self::Request,
206        wire: &Descriptor<'_>,
207    ) -> Result<Self::Request, ProviderError> {
208        let _ = wire;
209        Ok(request)
210    }
211
212    /// The normalized usage one reply's `end` carries. The driver hands it
213    /// to the attempt's one closing call when the reply ends, so the
214    /// observation is the usage the decoder already normalized and never a
215    /// second parse of the payload. Observes nothing by default.
216    fn observed_usage(end: &Self::End) -> Option<&crate::completion::Usage> {
217        let _ = end;
218        None
219    }
220}
221
222/// What a model keeps from preparing one request for the next
223/// ([`Descriptor::prepared`]): a completion's history as it was last
224/// adapted, so a history that grew is adapted only from its last turn on. A
225/// model's clones share it.
226#[derive(Clone, Debug, Default)]
227pub struct Prepared(pub(crate) Arc<Mutex<crate::completion::history::Memo>>);
228
229/// What a model kept never makes two models differ.
230impl PartialEq for Prepared {
231    fn eq(&self, _: &Self) -> bool {
232        true
233    }
234}
235
236/// Events a decoder builds and writes with [`Out::event`].
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum Free {}
239
240/// Events only the completion writer's part handles build.
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum Assembled {}
243
244/// Who builds an operation's events: [`Free`] or [`Assembled`]. Sealed.
245pub trait Emit<Op: Operation>: reply::Closing<Op> {}
246
247impl<Op: Operation> reply::Closing<Op> for Free {
248    fn close(_shared: &mut Shared<Op>) {}
249}
250
251impl<Op: Operation> Emit<Op> for Free {}
252
253/// What the driver knows about a call before its first frame.
254pub struct Call<'a> {
255    /// What the wire says about itself.
256    pub wire: &'a Descriptor<'a>,
257    /// How the reply arrives.
258    pub mode: Mode,
259    pub(crate) span: tracing::Span,
260}
261
262impl<'a> Call<'a> {
263    /// A call to the wire `wire` describes, in `mode`, under no span.
264    pub(crate) fn new(wire: &'a Descriptor<'a>, mode: Mode) -> Self {
265        Self {
266            wire,
267            mode,
268            span: tracing::Span::none(),
269        }
270    }
271
272    /// Run the call under `span`: a unary call sends under it and a stream
273    /// decodes under it.
274    pub fn instrument(&mut self, span: tracing::Span) {
275        self.span = span;
276    }
277}
278
279/// One reply's state for an operation.
280///
281/// Each event is [`absorb`](Self::absorb)ed as the consumer takes it, so
282/// the fold never runs ahead of what was delivered. The reply's end is what
283/// [`finish`](Self::finish) needs: without the provider's end there is no
284/// response.
285pub trait Fold<Op: Operation> {
286    /// Absorb one event the consumer is about to see. An error fails the
287    /// reply.
288    fn absorb(&mut self, event: &Op::Event) -> Result<(), ProviderError>;
289
290    /// The response, from the events absorbed, the provider's end and what
291    /// the driver learned about the reply.
292    fn finish(self, end: Op::End, reply: Reply) -> Result<Op::Response, ProviderError>;
293}
294
295/// What the driver learned about a reply beyond its events.
296#[derive(Debug, Clone, PartialEq)]
297pub struct Reply {
298    /// The provider descriptor name, for the response's `provider` field.
299    pub provider: String,
300    /// The reply's provider document (a completion's `raw`): the whole body
301    /// when it is one JSON document, else what the wire's reassembler
302    /// rebuilt or a free-event decoder recorded, else `Null`.
303    pub raw: serde_json::Value,
304    /// The provider's transport request id from the reply headers.
305    pub provider_request_id: Option<String>,
306}
307
308/// Whether a decoder step left the reply open. Only [`Out::end`] makes an
309/// [`Ended`].
310#[derive(Debug)]
311#[must_use]
312pub enum Flow {
313    /// The reply continues.
314    More,
315    /// The provider ended the reply; nothing is read after it.
316    Ended(Ended),
317}
318
319/// Proof that a decoder ended its reply with [`Out::end`].
320#[derive(Debug)]
321pub struct Ended(());
322
323pub(crate) use reply::Shared;
324
325pub(crate) mod reply {
326    use std::collections::VecDeque;
327
328    use super::{Fold, Operation, ProviderError, Reply};
329    use crate::streaming::Item;
330
331    /// One reply's items and end, shared by the driver that writes them and
332    /// the stream that takes them.
333    pub struct Shared<Op: Operation> {
334        pub(crate) fold: Op::Fold,
335        pub(crate) items: VecDeque<Result<Item<Op::Event>, ProviderError>>,
336        pub(crate) end: Option<Op::End>,
337        pub(crate) raw: Option<serde_json::Value>,
338        /// The response a relayed reply's origin already folded.
339        pub(crate) response: Option<Op::Response>,
340        /// What the transport reported: the reply's request id, its whole
341        /// document and its request path.
342        pub(crate) request_id: Option<String>,
343        pub(crate) document: Option<serde_json::Value>,
344        pub(crate) route: String,
345    }
346
347    impl<Op: Operation> Shared<Op> {
348        pub(crate) fn new(fold: Op::Fold) -> Self {
349            Self {
350                fold,
351                items: VecDeque::new(),
352                end: None,
353                raw: None,
354                response: None,
355                request_id: None,
356                document: None,
357                route: String::new(),
358            }
359        }
360
361        /// Take the next item: an event is absorbed by the fold before it
362        /// leaves, and an error is stamped with the reply's request id.
363        pub(crate) fn take(&mut self) -> Option<Result<Item<Op::Event>, ProviderError>> {
364            Some(match self.items.pop_front()? {
365                Ok(Item::Event(event)) => self.fold.absorb(&event).map(|()| Item::Event(event)),
366                Ok(unknown) => Ok(unknown),
367                // An id an upstream constructor already attached wins: it
368                // saw the reply.
369                Err(error) => Err(error.with_provider_request_id(self.request_id.clone())),
370            })
371        }
372
373        /// What the driver learned about the reply so far, from `provider`.
374        pub(crate) fn reply(&self, provider: &str) -> Reply {
375            reply(
376                provider,
377                self.document.clone().or_else(|| self.raw.clone()),
378                self.request_id.clone(),
379            )
380        }
381
382        /// Fold the absorbed reply with the provider's end into the
383        /// response, or return the response a relay's origin folded. A
384        /// reply the provider did not end is [`ProviderError::Truncated`].
385        pub(crate) fn conclude(self, provider: &str) -> Result<Op::Response, ProviderError> {
386            if let Some(response) = self.response {
387                return Ok(response);
388            }
389            let reply = reply(provider, self.document.or(self.raw), self.request_id);
390            let end = self.end.ok_or(ProviderError::Truncated)?;
391            self.fold.finish(end, reply)
392        }
393    }
394
395    /// The reply from `provider` whose document is `raw`: a whole body, or
396    /// what the wire's reassembler rebuilt of a stream, or what the decoder
397    /// of an operation with free events recorded.
398    fn reply(provider: &str, raw: Option<serde_json::Value>, request_id: Option<String>) -> Reply {
399        Reply {
400            provider: provider.to_owned(),
401            raw: raw.unwrap_or(serde_json::Value::Null),
402            provider_request_id: request_id,
403        }
404    }
405
406    /// What the reply's end closes before it is recorded.
407    pub trait Closing<Op: Operation> {
408        fn close(shared: &mut Shared<Op>);
409    }
410}
411
412/// What a runtime accounts for about a model. Each field matters to the
413/// operations that name it and is left at its default by the others.
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
415pub struct Capabilities {
416    /// What a completion provider composes.
417    pub completion: crate::completion::ProviderCapabilities,
418    /// The most documents one embedding or reranking request takes.
419    pub max_documents: usize,
420    /// The dimensionality of the returned vectors. Zero is unknown.
421    pub ndims: usize,
422    /// The embedding width the caller asked for, when they named one. `None`
423    /// and zero disable the check that replies honour it.
424    pub declared: Option<usize>,
425}
426
427impl Capabilities {
428    /// A completion provider's capabilities.
429    pub const fn completion(completion: crate::completion::ProviderCapabilities) -> Self {
430        Self {
431            completion,
432            ..Self::embedding(0, 0)
433        }
434    }
435
436    /// An embedding model's batch limit and width, with no width declared.
437    pub const fn embedding(max_documents: usize, ndims: usize) -> Self {
438        Self {
439            completion: crate::completion::ProviderCapabilities::new(),
440            max_documents,
441            ndims,
442            declared: None,
443        }
444    }
445
446    /// A reranking model's batch limit.
447    pub const fn rerank(max_documents: usize) -> Self {
448        Self::embedding(max_documents, 0)
449    }
450
451    /// Record the width the caller named, when they named one.
452    pub const fn declaring(mut self, declared: Option<usize>) -> Self {
453        self.declared = declared;
454        self
455    }
456
457    /// Returns [`ProviderError::MismatchedDimensions`] for the first width
458    /// differing from a positive caller declaration. Otherwise succeeds.
459    pub(crate) fn honour_declaration(
460        &self,
461        provider: &str,
462        widths: impl IntoIterator<Item = usize>,
463    ) -> Result<(), ProviderError> {
464        // Zero is rig's sentinel for an unknown width, never a claim about
465        // one: a model absent from every table this build knows resolves to
466        // it, and treating that as a declaration would fail every reply.
467        let Some(requested) = self.declared.filter(|declared| *declared > 0) else {
468            return Ok(());
469        };
470        let Some(returned) = widths.into_iter().find(|width| *width != requested) else {
471            return Ok(());
472        };
473        Err(ProviderError::MismatchedDimensions {
474            provider: provider.to_owned(),
475            requested,
476            returned,
477        })
478    }
479}
480
481/// What a wire says about itself: plain data, read before every call.
482#[derive(Debug, Clone)]
483pub struct Descriptor<'a> {
484    /// The model replay shapes a completion history for. Every completion
485    /// wire names one; other operations leave it `None`.
486    pub replay: Option<&'a dyn crate::completion::ReplayTarget>,
487    /// The provider descriptor name (`"anthropic"`), as records and
488    /// telemetry name it.
489    pub name: &'a str,
490    /// The model id the wire addresses, for telemetry. `None` for
491    /// operations that address no model.
492    pub model: Option<&'a str>,
493    /// What a runtime accounts for.
494    pub capabilities: Capabilities,
495    /// The telemetry operation for a call in each mode, when the endpoint
496    /// has a canonical name of its own (Gemini `generate_content`). The mode
497    /// itself is recorded separately, as `gen_ai.request.stream`.
498    pub telemetry: Option<fn(Mode) -> crate::telemetry::GenAiOperation>,
499    /// The model facts a completion wire encodes with, which its replies are
500    /// priced by. `None` answers from the built-in catalog.
501    pub facts: Option<&'a crate::catalog::ModelFacts>,
502    /// What the model kept from preparing its last request, which the
503    /// driver lends [`Operation::prepare`]; `None` from the wire itself.
504    pub prepared: Option<&'a Prepared>,
505}
506
507impl<'a> Descriptor<'a> {
508    /// A wire named `name`, addressing no model, with default capabilities.
509    pub fn new(name: &'a str) -> Self {
510        Self {
511            replay: None,
512            name,
513            model: None,
514            capabilities: Capabilities::default(),
515            telemetry: None,
516            facts: None,
517            prepared: None,
518        }
519    }
520
521    /// The spec of the model the wire addresses, from its facts under its
522    /// own name: the spec it was connected to, else the catalog's entry.
523    /// `None` for a wire that addresses no model, or a model the catalog
524    /// does not list.
525    pub fn spec(&self) -> Option<&'a crate::catalog::ModelSpec> {
526        let model = self.model?;
527        match self.facts {
528            Some(facts) => facts.for_model(self.name, model),
529            None => crate::catalog::Catalog::builtin()
530                .get_vendor(self.name, model)
531                .map(|resolved| resolved.spec),
532        }
533    }
534
535    /// The model replay shapes a completion history for.
536    pub fn replay(mut self, target: &'a dyn crate::completion::ReplayTarget) -> Self {
537        self.replay = Some(target);
538        self
539    }
540
541    /// The model id the wire addresses, when it addresses one.
542    pub fn model(mut self, model: impl Into<Option<&'a str>>) -> Self {
543        self.model = model.into();
544        self
545    }
546
547    /// What a runtime accounts for.
548    pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
549        self.capabilities = capabilities;
550        self
551    }
552
553    /// The model facts the wire encodes with.
554    pub fn facts(mut self, facts: &'a crate::catalog::ModelFacts) -> Self {
555        self.facts = Some(facts);
556        self
557    }
558
559    /// The endpoint's own telemetry operation for each mode.
560    pub fn telemetry(mut self, telemetry: fn(Mode) -> crate::telemetry::GenAiOperation) -> Self {
561        self.telemetry = Some(telemetry);
562        self
563    }
564}
565
566/// One classified wire frame.
567#[derive(Debug)]
568pub enum WireEvent<T> {
569    /// The frame carries a discriminator this client models and its payload
570    /// decoded fully.
571    Known(T),
572    /// Valid JSON not recognized by this classifier.
573    /// Drivers log structural metadata only and skip interpretation.
574    Unknown {
575        /// The unmodeled discriminator value.
576        event_type: String,
577        /// Full payload for raw passthrough, never warning logs. Debug is redacted.
578        value: crate::streaming::UnknownPayload,
579    },
580    /// Invalid JSON or a recognized frame that failed typed decoding.
581    /// Must not be demoted to `Unknown`.
582    Corrupt(serde_json::Error),
583}
584
585impl<T> WireEvent<T> {
586    /// Map the `Known` payload, preserving the classification.
587    ///
588    /// This is how an adapter layers a pure event-shape mapping on top of a
589    /// classifier without restating the triage: `Unknown` and `Corrupt` pass
590    /// through untouched, so policy stays with the driver.
591    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> WireEvent<U> {
592        match self {
593            Self::Known(event) => WireEvent::Known(f(event)),
594            Self::Unknown { event_type, value } => WireEvent::Unknown { event_type, value },
595            Self::Corrupt(error) => WireEvent::Corrupt(error),
596        }
597    }
598}
599
600/// Where a decoder writes one reply. The `'id` brand ties it to that one
601/// reply: a writer cannot be kept past the decode step that received it.
602///
603/// ```compile_fail
604/// use rig_core::operation::Completion;
605/// use rig_core::wire::Out;
606///
607/// // A writer cannot be stashed to write into the reply later.
608/// struct Stash(Option<Out<'static, Completion>>);
609///
610/// fn keep<'id>(stash: &mut Stash, out: Out<'id, Completion>) {
611///     stash.0 = Some(out);
612/// }
613/// ```
614pub struct Out<'id, Op: Operation> {
615    shared: &'id std::sync::Mutex<Shared<Op>>,
616    brand: std::marker::PhantomData<fn(&'id ()) -> &'id ()>,
617}
618
619impl<'id, Op: Operation> Out<'id, Op> {
620    pub(crate) fn new(shared: &'id std::sync::Mutex<Shared<Op>>) -> Self {
621        Self {
622            shared,
623            brand: std::marker::PhantomData,
624        }
625    }
626
627    pub(crate) fn lock(&self) -> std::sync::MutexGuard<'id, Shared<Op>> {
628        self.shared
629            .lock()
630            .unwrap_or_else(std::sync::PoisonError::into_inner)
631    }
632
633    /// End the reply with what the provider sent at its end. It consumes the
634    /// writer: nothing is written after the end.
635    pub fn end(self, end: Op::End) -> Flow {
636        let mut shared = self.lock();
637        <Op::Emit as reply::Closing<Op>>::close(&mut shared);
638        shared.end = Some(end);
639        Flow::Ended(Ended(()))
640    }
641
642    /// A payload the provider sent that this decoder does not model. It
643    /// reaches the consumer as [`Item::Unknown`](crate::streaming::Item).
644    pub fn unknown(&mut self, payload: crate::streaming::UnknownPayload) {
645        self.lock()
646            .items
647            .push_back(Ok(crate::streaming::Item::Unknown(payload)));
648    }
649}
650
651impl<Op: Operation<Emit = Free>> Out<'_, Op> {
652    /// Record the reply's provider document, the response's `raw`. A whole
653    /// JSON body the transport reported outranks it: a decoder whose reply
654    /// is not one JSON document records it here.
655    ///
656    /// Only an operation whose decoders build their own events has this. A
657    /// completion's `raw` is what its wire's
658    /// [`Reassembler`](Wire::Reassembler) rebuilds, never what a decoder
659    /// writes:
660    ///
661    /// ```compile_fail,E0599
662    /// use rig_core::operation::Completion;
663    /// use rig_core::wire::Out;
664    ///
665    /// fn record(out: &mut Out<'_, Completion>) {
666    ///     out.raw(serde_json::json!({ "response_id": "x" }));
667    /// }
668    /// ```
669    pub fn raw(&mut self, raw: serde_json::Value) {
670        self.lock().raw = Some(raw);
671    }
672
673    /// One event of the reply.
674    ///
675    /// Only an operation whose decoders build their own events has this; a
676    /// completion's events come from its part handles:
677    ///
678    /// ```compile_fail,E0599
679    /// use rig_core::operation::Completion;
680    /// use rig_core::streaming::StreamEvent;
681    /// use rig_core::wire::Out;
682    ///
683    /// fn reinject(out: &mut Out<'_, Completion>, seen: &StreamEvent) {
684    ///     out.event(seen.clone());
685    /// }
686    /// ```
687    ///
688    /// An event rebuilt from its serialized form is no different:
689    ///
690    /// ```compile_fail,E0599
691    /// use rig_core::operation::Completion;
692    /// use rig_core::streaming::StreamRecord;
693    /// use rig_core::wire::Out;
694    ///
695    /// fn rebuild(out: &mut Out<'_, Completion>, recorded: serde_json::Value) {
696    ///     let Ok(transcript) = StreamRecord::parse(recorded) else { return };
697    ///     for event in transcript.events() {
698    ///         out.event(event.clone());
699    ///     }
700    /// }
701    /// ```
702    pub fn event(&mut self, event: Op::Event) {
703        self.lock()
704            .items
705            .push_back(Ok(crate::streaming::Item::Event(event)));
706    }
707}
708
709/// Synchronous state machine for one reply. It classifies frames and
710/// decodes known events into the reply's writer, without transport access
711/// and without being told how the reply arrives. HTTP wires read
712/// [`WireFrame`]s; other transports name their own frame type.
713pub trait Decoder<'id, Op: Operation, Frame = WireFrame> {
714    /// The wire's typed event, produced by this decoder's classifier.
715    type Event;
716
717    /// Decode and classify one frame. A JSON wire MUST delegate to a
718    /// classifier in [`crate::providers::internal::wire`], so the
719    /// decode-then-validate policy is not re-derived per provider.
720    fn classify(&self, frame: Frame) -> WireEvent<Self::Event>;
721
722    /// Write one `Known` event into the reply. An error ends the reply with
723    /// it.
724    fn decode(&mut self, event: Self::Event, out: Out<'id, Op>) -> Result<Flow, ProviderError>;
725
726    /// The frames ran out without an end. A decoder that already saw the
727    /// provider's end, in a form the provider sends before its last frame,
728    /// ends the reply here; otherwise the reply is truncated.
729    fn eof(&mut self, out: Out<'id, Op>) -> Result<Flow, ProviderError> {
730        let _ = out;
731        Err(ProviderError::Truncated)
732    }
733}
734
735/// A provider endpoint: plain data that encodes requests and names a decoder
736/// for each reply.
737///
738/// No transport, no future, no type parameter. Implementations are plain
739/// data (`Clone + PartialEq + Debug + Serialize + Deserialize`, with
740/// credentials held in [`Secret`]), so a host can store one in a scene, a
741/// component, or a config file. `Clone` is a supertrait: the driver clones
742/// the wire into every call's `'static` stream.
743pub trait Wire: Clone + WasmCompatSend + WasmCompatSync + 'static {
744    /// The operation this wire performs.
745    type Op: Operation;
746    /// What [`Self::encode`] produces for the transport to send.
747    type Payload: WasmCompatSend + 'static;
748    /// One unit of a reply, as the transport delivers it.
749    type Frame: WasmCompatSend + 'static;
750    /// The decoder's classified event, which the reassembler reads too.
751    type Event: WasmCompatSend + 'static;
752    /// The decoder for one of its replies, branded with that reply.
753    type Decoder<'id>: Decoder<'id, Self::Op, Self::Frame, Event = Self::Event> + WasmCompatSend;
754    /// What rebuilds a reply's provider document from its events when the
755    /// transport reports no whole document, as for a stream. The driver
756    /// hands it every known event before the decoder decodes it, and
757    /// records what it finishes with as `raw`. A wire whose operation's
758    /// decoders record `raw` themselves names [`document::Unreassembled`];
759    /// a completion wire cannot, because it does not
760    /// [`Serve`](document::Serves) completions.
761    type Reassembler: document::Reassemble<Self::Event> + document::Serves<Self::Op>;
762
763    /// What the wire says about itself.
764    fn describe(&self) -> Descriptor<'_>;
765
766    /// The request to send. Pure: it may read `self`, `request` and `mode`,
767    /// and nothing else. A request that cannot be built is an
768    /// [`EncodeError`], which always reports as a request failure.
769    fn encode(&self, request: Request<Self>, mode: Mode) -> Result<Self::Payload, EncodeError>;
770
771    /// A fresh decoder for one reply.
772    fn decoder<'id>(&self) -> Self::Decoder<'id>;
773
774    /// A fresh reassembler for one reply. A wire that picks its API per
775    /// reply builds the matching one.
776    fn reassembler(&self) -> Self::Reassembler {
777        Self::Reassembler::default()
778    }
779}
780
781/// One reply of `wire` to `request`, decoded from frames already in hand as
782/// a live call in `mode` would decode them: through the wire's decoder,
783/// reassembler and fold, with no transport. A reply that arrives some other
784/// way, such as a batch result's body ([`WireFrame::Bytes`], unary) or a
785/// recorded stream, becomes an ordinary response. `raw` is what the wire's
786/// reassembler rebuilds from the frames, and the request is the one sent,
787/// as [`Wire::encode`] took it.
788///
789/// ```
790/// use rig_core::completion::{CompletionRequest, CompletionResponse};
791/// use rig_core::error::ProviderError;
792/// use rig_core::providers::anthropic::wire::Messages;
793/// use rig_core::wire::{Mode, WireFrame, decode_reply};
794///
795/// /// A batch result's body, as the reply to the `request` it answers.
796/// fn batch_reply(
797///     wire: &Messages,
798///     request: &CompletionRequest,
799///     body: Vec<u8>,
800/// ) -> Result<CompletionResponse, ProviderError> {
801///     decode_reply(wire, request, Mode::Unary, [WireFrame::Bytes(body)])
802/// }
803/// ```
804///
805/// # Errors
806///
807/// What the reply fails with when called live: a frame the decoder rejects,
808/// a provider error in the reply, or [`ProviderError::Truncated`] when the
809/// frames end before the provider ended the reply.
810pub fn decode_reply<W: Wire>(
811    wire: &W,
812    request: &Request<W>,
813    mode: Mode,
814    frames: impl IntoIterator<Item = W::Frame>,
815) -> Result<Response<W>, ProviderError> {
816    crate::driver::decode_reply(wire, request, mode, frames, serde_json::Value::Null)
817}
818
819/// The decoder of a reply that is one JSON document: the document is the
820/// operation's end, and is kept as the reply's `raw`.
821#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
822pub struct Json;
823
824impl<'id, Op> Decoder<'id, Op, WireFrame> for Json
825where
826    Op: Operation<Emit = Free>,
827    Op::End: serde::de::DeserializeOwned,
828{
829    type Event = (Op::End, serde_json::Value);
830
831    fn classify(&self, frame: WireFrame) -> WireEvent<Self::Event> {
832        let document = match serde_json::from_str::<serde_json::Value>(&frame.as_str()) {
833            Ok(document) => document,
834            Err(error) => return WireEvent::Corrupt(error),
835        };
836        match <Op::End as serde::Deserialize>::deserialize(&document) {
837            Ok(end) => WireEvent::Known((end, document)),
838            Err(error) => WireEvent::Corrupt(error),
839        }
840    }
841
842    fn decode(
843        &mut self,
844        (end, document): Self::Event,
845        mut out: Out<'id, Op>,
846    ) -> Result<Flow, ProviderError> {
847        out.raw(document);
848        Ok(out.end(end))
849    }
850}
851
852/// The decoder of a reply that is one document: `classify` reads the
853/// document, and `end` makes it the operation's end.
854pub struct DocumentDecoder<Op: Operation, Event> {
855    classify: fn(&str) -> WireEvent<Event>,
856    end: fn(Event) -> Result<Op::End, ProviderError>,
857}
858
859impl<Op: Operation, Event> DocumentDecoder<Op, Event> {
860    /// The decoder that reads a reply with `classify` and ends it with `end`.
861    pub fn new(
862        classify: fn(&str) -> WireEvent<Event>,
863        end: fn(Event) -> Result<Op::End, ProviderError>,
864    ) -> Self {
865        Self { classify, end }
866    }
867}
868
869impl<'id, Op: Operation, Event> Decoder<'id, Op> for DocumentDecoder<Op, Event> {
870    type Event = Event;
871
872    fn classify(&self, frame: WireFrame) -> WireEvent<Event> {
873        (self.classify)(&frame.as_str())
874    }
875
876    fn decode(&mut self, event: Event, out: Out<'id, Op>) -> Result<Flow, ProviderError> {
877        Ok(out.end((self.end)(event)?))
878    }
879}
880
881/// A wire's request type.
882pub type Request<W> = <<W as Wire>::Op as Operation>::Request;
883/// A wire's response type.
884pub type Response<W> = <<W as Wire>::Op as Operation>::Response;
885/// A wire's event type.
886pub type Event<W> = <<W as Wire>::Op as Operation>::Event;
887
888#[cfg(test)]
889mod tests;