1#[cfg(feature = "exhaustive")]
13macro_rules! wire {
14 ($value:expr) => {
15 $value
16 };
17}
18
19#[cfg(not(feature = "exhaustive"))]
20macro_rules! wire {
21 ($($tokens:tt)*) => {
22 gproxy_protocol::wire!($($tokens)*)
23 };
24}
25
26pub(crate) use wire;
27
28mod common;
29mod compact;
30mod count_tokens;
31mod embeddings;
32mod envelope;
33mod error;
34mod generate_content;
35mod images;
36mod models;
37mod registry;
38pub mod typed;
39mod videos;
40
41use bytes::Bytes;
42use gproxy_protocol::{OperationKey, StreamFraming};
43
44pub use envelope::{BufferedResponse, ResponseCollector, ResponseStream, synthesize_response};
45pub use error::TransformError;
46pub use gproxy_protocol as protocol;
47
48pub fn can_transform(source: OperationKey, target: OperationKey) -> bool {
49 envelope::is_promotion(source, target) || registry::resolve(source, target).is_some()
50}
51
52pub fn request(
53 source: OperationKey,
54 target: OperationKey,
55 mut body: Bytes,
56 upstream_model: &str,
57 stream: bool,
58) -> Result<Bytes, TransformError> {
59 if envelope::is_promotion(source, target) {
60 return envelope::promotion_request(source, target, body);
61 }
62 let original_target = target;
63 let semantic_source = semantic_responses_key(source);
64 let semantic_target = semantic_responses_key(target);
65 if semantic_source != source {
66 body = envelope::promotion_request(source, semantic_source, body)?;
67 }
68 let pair = registry::resolve(semantic_source, semantic_target).ok_or(
69 TransformError::UnsupportedPair {
70 source_key: source,
71 target_key: target,
72 },
73 )?;
74 let body = registry::request(pair, body, upstream_model, stream)?;
75 if semantic_target != original_target {
76 envelope::promotion_request(semantic_target, original_target, body)
77 } else {
78 Ok(body)
79 }
80}
81
82pub fn response(
83 source: OperationKey,
84 target: OperationKey,
85 body: Bytes,
86) -> Result<Bytes, TransformError> {
87 if envelope::is_promotion(source, target) {
88 return envelope::promotion_response(body);
89 }
90 let semantic_source = semantic_responses_key(source);
91 let semantic_target = semantic_responses_key(target);
92 let pair = registry::resolve(semantic_source, semantic_target).ok_or(
93 TransformError::UnsupportedPair {
94 source_key: source,
95 target_key: target,
96 },
97 )?;
98 registry::response(pair, body)
99}
100
101fn semantic_responses_key(key: OperationKey) -> OperationKey {
102 if key.kind()
103 == gproxy_protocol::OperationKind::ContentGeneration(
104 gproxy_protocol::ContentGenerationKind::OpenAiResponsesWebSocket,
105 )
106 {
107 return OperationKey::content(
108 key.operation(),
109 gproxy_protocol::ContentGenerationKind::OpenAiResponses,
110 );
111 }
112 key
113}
114
115pub fn response_stream(
116 source: OperationKey,
117 target: OperationKey,
118) -> Result<ResponseStream, TransformError> {
119 ResponseStream::new(source, target)
120}
121
122pub fn response_stream_framed(
123 source: OperationKey,
124 target: OperationKey,
125 source_framing: StreamFraming,
126 target_framing: StreamFraming,
127) -> Result<ResponseStream, TransformError> {
128 ResponseStream::new_framed(source, target, source_framing, target_framing)
129}
130
131pub fn request_query(
132 source: OperationKey,
133 target: OperationKey,
134 query: Option<&str>,
135) -> Result<Option<String>, TransformError> {
136 if source.operation() != gproxy_protocol::Operation::ListModels {
137 return Ok(query.map(str::to_owned));
138 }
139 use gproxy_protocol::{OperationKind::Family, WireFamily};
140 Ok(match (source.kind(), target.kind()) {
141 (Family(WireFamily::Claude), Family(WireFamily::Gemini)) => {
142 let values = query_pairs(query);
143 let mut output = Vec::new();
144 copy_query(&values, "limit", "pageSize", &mut output);
145 copy_query(&values, "after_id", "pageToken", &mut output);
146 joined_query(output)
147 }
148 (Family(WireFamily::Gemini), Family(WireFamily::Claude)) => {
149 let values = query_pairs(query);
150 let mut output = Vec::new();
151 copy_query(&values, "pageSize", "limit", &mut output);
152 copy_query(&values, "pageToken", "after_id", &mut output);
153 joined_query(output)
154 }
155 (Family(WireFamily::Claude | WireFamily::Gemini), Family(WireFamily::OpenAi))
156 | (Family(WireFamily::OpenAi), Family(WireFamily::Claude | WireFamily::Gemini)) => None,
157 _ => query.map(str::to_owned),
158 })
159}
160
161fn query_pairs(query: Option<&str>) -> Vec<(String, String)> {
162 form_urlencoded::parse(query.unwrap_or_default().as_bytes())
163 .map(|(key, value)| (key.into_owned(), value.into_owned()))
164 .collect()
165}
166
167fn copy_query(
168 values: &[(String, String)],
169 source: &str,
170 target: &str,
171 output: &mut Vec<(String, String)>,
172) {
173 if let Some((_, value)) = values.iter().find(|(key, _)| key == source) {
174 output.push((target.into(), value.clone()));
175 }
176}
177
178fn joined_query(values: Vec<(String, String)>) -> Option<String> {
179 if values.is_empty() {
180 None
181 } else {
182 Some(
183 form_urlencoded::Serializer::new(String::new())
184 .extend_pairs(values)
185 .finish(),
186 )
187 }
188}
189
190#[cfg(test)]
191mod tests;