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;