pub struct VendorFrames { /* private fields */ }Expand description
Surfaces vendor frames: stream frames of a type an adapter does not model.
Endpoints and proxies add frames to standard streams: a named SSE event
(event: search_results), an NDJSON line of a type the API does not
define. An adapter never decodes them as its own and never drops them:
surface turns each one into events, as it arrives,
and finish completes them when the response ends.
§Events
- The first frame of a type is preceded by one
Warning(WarningKind::Other,Subject::Response, with the provider name). - Each frame yields a
ResponseProviderDataevent under the adapter’s namespace and the frame type, holding that frame’s payload alone, as a one-element array: a live consumer sees each payload once, as it arrives, in constant work per frame. finish, called by the decoder just before itsResponseStop, yields one moreResponseProviderDataevent holding the whole array, in arrival order, of every type that arrived more than once.ProviderData::mergereplaces a value by key, so a collector that merges the events ends with every payload of every type: the collected response’s provider data holds the full arrays.
Before 2026-10-07 each event carried the whole array so far, which made a
stream with a vendor frame per chunk quadratic in time and in what a
recording holds. An adapter that forgets finish keeps
only the last payload of a repeated type in its collected response.
A vendor frame never starts the response. Yield the returned events as
they come, also before ResponseStart:
Prepared::finish_stream holds
them until the start (start_first).
allama_conformance::stream_frames::assert_unknown_stream_frames_surfaced
checks the result.
use allama::{StreamEvent, WarningKind};
use allama_http::framing::VendorFrames;
let mut frames = VendorFrames::new("demo", "openai-compat:demo");
let first = frames.surface("search_results", r#"{"url":"https://a"}"#);
assert!(matches!(
&first[0],
StreamEvent::Warning { warning, .. } if warning.kind == WarningKind::Other
));
let second = frames.surface("search_results", "not json");
let StreamEvent::ResponseProviderData { provider_data, .. } = &second[0] else {
panic!("{second:?}")
};
assert_eq!(
provider_data.get_raw("openai-compat:demo", "search_results"),
Some(&serde_json::json!(["not json"])),
);
// Before `ResponseStop`: the whole array of each repeated type.
let Some(StreamEvent::ResponseProviderData { provider_data, .. }) = frames.finish() else {
panic!()
};
assert_eq!(
provider_data.get_raw("openai-compat:demo", "search_results"),
Some(&serde_json::json!([{"url": "https://a"}, "not json"])),
);Implementations§
Source§impl VendorFrames
impl VendorFrames
Sourcepub fn new(
provider: impl Into<Cow<'static, str>>,
namespace: impl Into<Cow<'static, str>>,
) -> Self
pub fn new( provider: impl Into<Cow<'static, str>>, namespace: impl Into<Cow<'static, str>>, ) -> Self
Vendor frames of a provider (for the warnings), surfaced under its
namespace.
Sourcepub fn surface(&mut self, event: &str, data: &str) -> Vec<StreamEvent>
pub fn surface(&mut self, event: &str, data: &str) -> Vec<StreamEvent>
Surface the raw data of a frame of type event: kept as JSON when
it parses, as the trimmed text otherwise. Empty data surfaces
nothing.
Sourcepub fn surface_payload(
&mut self,
event: &str,
payload: Value,
) -> Vec<StreamEvent>
pub fn surface_payload( &mut self, event: &str, payload: Value, ) -> Vec<StreamEvent>
Surface a payload already decoded, for a frame whose type arrives
inside its data (an NDJSON line’s type, an SSE event’s JSON
type) rather than as an SSE event name.
Sourcepub fn finish(&mut self) -> Option<StreamEvent>
pub fn finish(&mut self) -> Option<StreamEvent>
The event that completes the surfaced frames, for the decoder to
yield just before its ResponseStop: one
ResponseProviderData holding
the whole array of every type that arrived more than once, which
replaces the single payloads a collector merged. None when no type
repeated (the events already carry every payload). The frames are
taken: a second call yields nothing.