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