Skip to main content

gproxy_protocol/adapt/generate/stream/event/
mod.rs

1//! Concrete native event collection. The associated payloads remain vendor wire types.
2
3mod chat;
4mod claude;
5mod gemini;
6mod responses;
7use super::super::{
8    ToolCallKind,
9    identity_facts::{IdentityFacts, ToolIdentity},
10};
11use crate::{
12    Dialect,
13    transform::{
14        Converted, TransformError,
15        identity::{IdentityFlow, TargetIdPolicy},
16    },
17    wire::DeclaredFields,
18};
19use serde::{Serialize, de::DeserializeOwned};
20
21#[derive(Debug, Clone, Copy)]
22pub struct EventLimits {
23    pub max_bytes: usize,
24    pub max_pending_bytes: usize,
25}
26
27impl EventLimits {}
28
29#[derive(Debug, Clone)]
30pub struct Collected<T> {
31    pub value: T,
32    /// Actual source observations override IDs synthesized only to construct a
33    /// complete native Chat DTO. These are never invented upstream identities.
34    pub(crate) original_tool_ids: Option<Vec<Option<String>>>,
35}
36
37impl<T> Collected<T> {
38    fn native(value: T) -> Self {
39        Self {
40            value,
41            original_tool_ids: None,
42        }
43    }
44}
45
46impl<T: IdentityFacts> IdentityFacts for Collected<T> {
47    fn dialect(&self) -> Dialect {
48        self.value.dialect()
49    }
50    fn tools(&self) -> Vec<ToolIdentity> {
51        let mut tools = self.value.tools();
52        if let Some(ids) = &self.original_tool_ids {
53            // The DTO's native legacy/modern form remains intact. Only IDs
54            // synthesized for DTO completeness are replaced by actual observations.
55            for (tool, id) in tools.iter_mut().zip(ids) {
56                tool.call_id.clone_from(id);
57            }
58        }
59        tools
60    }
61}
62
63pub(crate) mod sealed {
64    pub trait Event {}
65}
66
67/// Sealed over the four actual generation event types.
68pub trait NativeEvent:
69    sealed::Event + Serialize + DeserializeOwned + DeclaredFields + Clone
70{
71    type Full: Serialize + Clone + DeclaredFields;
72    type Collector;
73    fn responses_history(
74        _value: &Self::Full,
75    ) -> Option<&crate::wire::openai::responses::GenerateContentResponseBody> {
76        None
77    }
78    const DIALECT: Dialect;
79    const DONE: bool = false;
80    fn decode(value: serde_json::Value) -> Result<Self, TransformError> {
81        decode_event(value)
82    }
83    fn collector(
84        flow: IdentityFlow,
85        policy: TargetIdPolicy,
86        limits: EventLimits,
87    ) -> Self::Collector;
88    fn collect(collector: &mut Self::Collector, event: Self) -> Result<(), TransformError>;
89    fn collect_done(_collector: &mut Self::Collector) -> Result<(), TransformError> {
90        Err(invalid("unexpected DONE for native dialect"))
91    }
92    fn collected(
93        collector: Self::Collector,
94    ) -> Result<Converted<Collected<Self::Full>>, TransformError>;
95    fn event_name(&self) -> Option<&'static str>;
96    fn is_terminal(&self) -> bool;
97    fn is_usage_only(&self) -> bool {
98        false
99    }
100    /// Complete emitted ID, actual call kind, and complete name if already known.
101    fn tool_declarations(
102        &self,
103        complete_tool_names: bool,
104    ) -> Vec<(String, ToolCallKind, Option<String>)>;
105}
106
107fn decode_event<T: DeserializeOwned + DeclaredFields>(
108    value: serde_json::Value,
109) -> Result<T, TransformError> {
110    serde_json::from_value(value)
111        .map(DeclaredFields::into_declared)
112        .map_err(|error| {
113            super::codec_error(crate::codec::CodecError::with_source(
114                crate::codec::CodecErrorKind::Json,
115                crate::codec::CodecErrorStage::Stream,
116                "invalid native event",
117                error,
118            ))
119        })
120}
121
122fn invalid(message: impl Into<String>) -> TransformError {
123    TransformError::invalid_result("generation.stream.event", message)
124}