1pub mod anthropic;
18pub mod model_pricing;
19pub mod openai_chat;
20pub mod openai_responses;
21pub mod optimization;
22pub mod request;
23pub mod resolve;
24pub mod response;
25pub mod streaming;
26pub mod traits;
27
28use nemo_relay_types::Json;
29
30use crate::error::{FlowError, Result};
31
32fn optional_bool(
33 obj: &serde_json::Map<String, Json>,
34 key: &str,
35 surface: &str,
36) -> Result<Option<bool>> {
37 match obj.get(key) {
38 Some(Json::Null) | None => Ok(None),
39 Some(Json::Bool(value)) => Ok(Some(*value)),
40 Some(_) => Err(FlowError::InvalidArgument(format!(
41 "{surface} {key} must be a boolean or null"
42 ))),
43 }
44}
45
46fn optional_u64(
47 obj: &serde_json::Map<String, Json>,
48 key: &str,
49 surface: &str,
50) -> Result<Option<u64>> {
51 match obj.get(key) {
52 Some(Json::Null) | None => Ok(None),
53 Some(value) if value.as_u64().is_some() => Ok(value.as_u64()),
54 Some(_) => Err(FlowError::InvalidArgument(format!(
55 "{surface} {key} must be a non-negative integer or null"
56 ))),
57 }
58}
59
60fn optional_i64(
61 obj: &serde_json::Map<String, Json>,
62 key: &str,
63 surface: &str,
64) -> Result<Option<i64>> {
65 match obj.get(key) {
66 Some(Json::Null) | None => Ok(None),
67 Some(value) if value.as_i64().is_some() => Ok(value.as_i64()),
68 Some(_) => Err(FlowError::InvalidArgument(format!(
69 "{surface} {key} must be an integer or null"
70 ))),
71 }
72}
73
74fn optional_f64(
75 obj: &serde_json::Map<String, Json>,
76 key: &str,
77 surface: &str,
78) -> Result<Option<f64>> {
79 match obj.get(key) {
80 Some(Json::Null) | None => Ok(None),
81 Some(value) if value.as_f64().is_some() => Ok(value.as_f64()),
82 Some(_) => Err(FlowError::InvalidArgument(format!(
83 "{surface} {key} must be a number or null"
84 ))),
85 }
86}
87
88fn optional_string(
89 obj: &serde_json::Map<String, Json>,
90 key: &str,
91 surface: &str,
92) -> Result<Option<String>> {
93 match obj.get(key) {
94 Some(Json::Null) | None => Ok(None),
95 Some(Json::String(value)) => Ok(Some(value.clone())),
96 Some(_) => Err(FlowError::InvalidArgument(format!(
97 "{surface} {key} must be a string or null"
98 ))),
99 }
100}
101
102fn optional_object(
103 obj: &serde_json::Map<String, Json>,
104 key: &str,
105 surface: &str,
106) -> Result<Option<Json>> {
107 match obj.get(key) {
108 Some(Json::Null) | None => Ok(None),
109 Some(value @ Json::Object(_)) => Ok(Some(value.clone())),
110 Some(_) => Err(FlowError::InvalidArgument(format!(
111 "{surface} {key} must be an object or null"
112 ))),
113 }
114}
115
116fn optional_array(
117 obj: &serde_json::Map<String, Json>,
118 key: &str,
119 surface: &str,
120) -> Result<Option<Json>> {
121 match obj.get(key) {
122 Some(Json::Null) | None => Ok(None),
123 Some(value @ Json::Array(_)) => Ok(Some(value.clone())),
124 Some(_) => Err(FlowError::InvalidArgument(format!(
125 "{surface} {key} must be an array or null"
126 ))),
127 }
128}
129
130fn encode_changed_items<T, F>(
131 edited: &[T],
132 baseline: &[T],
133 original: Option<&[Json]>,
134 encode: F,
135) -> Result<Vec<Json>>
136where
137 T: PartialEq,
138 F: FnMut(&T) -> Result<Json>,
139{
140 encode_changed_items_with_patch(
141 edited,
142 baseline,
143 original,
144 encode,
145 |original, _, _, baseline_value, edited_value| {
146 patch_changed_json(original, baseline_value, edited_value)
147 },
148 )
149}
150
151fn encode_changed_items_with_patch<T, F, P>(
152 edited: &[T],
153 baseline: &[T],
154 original: Option<&[Json]>,
155 mut encode: F,
156 mut patch: P,
157) -> Result<Vec<Json>>
158where
159 T: PartialEq,
160 F: FnMut(&T) -> Result<Json>,
161 P: FnMut(&Json, &T, &T, &Json, &Json) -> Result<Json>,
162{
163 let alignment = if original.is_some() {
164 align_changed_items(edited, baseline)?
165 } else {
166 vec![None; edited.len()]
167 };
168 edited
169 .iter()
170 .enumerate()
171 .map(|(index, item)| {
172 if let Some(baseline_index) = alignment[index] {
173 let baseline_item = &baseline[baseline_index];
174 if let Some(original_item) = original.and_then(|items| items.get(baseline_index)) {
175 if baseline_item == item {
176 return Ok(original_item.clone());
177 }
178 let baseline_value = encode(baseline_item)?;
179 let edited_value = encode(item)?;
180 return patch(
181 original_item,
182 baseline_item,
183 item,
184 &baseline_value,
185 &edited_value,
186 );
187 }
188 }
189 encode(item)
190 })
191 .collect()
192}
193
194fn align_changed_items<T: PartialEq>(edited: &[T], baseline: &[T]) -> Result<Vec<Option<usize>>> {
195 let mut alignment = vec![None; edited.len()];
196 let mut used = vec![false; baseline.len()];
197
198 for index in 0..edited.len().min(baseline.len()) {
199 if edited[index] == baseline[index] {
200 alignment[index] = Some(index);
201 used[index] = true;
202 }
203 }
204
205 let mut structurally_changed = edited.len() != baseline.len();
206 for (edited_index, item) in edited.iter().enumerate() {
207 if alignment[edited_index].is_some() {
208 continue;
209 }
210 if let Some(baseline_index) = baseline
211 .iter()
212 .enumerate()
213 .position(|(baseline_index, candidate)| !used[baseline_index] && candidate == item)
214 {
215 alignment[edited_index] = Some(baseline_index);
216 used[baseline_index] = true;
217 structurally_changed |= baseline_index != edited_index;
218 }
219 }
220
221 let unmatched_edited = alignment
222 .iter()
223 .enumerate()
224 .filter_map(|(index, item)| item.is_none().then_some(index))
225 .collect::<Vec<_>>();
226 let unmatched_baseline = used
227 .iter()
228 .enumerate()
229 .filter_map(|(index, item)| (!item).then_some(index))
230 .collect::<Vec<_>>();
231
232 if unmatched_edited.len() > 1 && unmatched_baseline.len() > 1 {
233 return Err(FlowError::InvalidArgument(
234 "cannot safely preserve provider fields for multiple edited array items without stable identities"
235 .into(),
236 ));
237 }
238
239 if !structurally_changed && edited.len() == baseline.len() {
240 for index in unmatched_edited {
241 alignment[index] = Some(index);
242 }
243 }
244
245 Ok(alignment)
246}
247
248fn patch_changed_array(original: &[Json], baseline: &[Json], edited: &[Json]) -> Result<Vec<Json>> {
249 let alignment = align_changed_items(edited, baseline)?;
250 edited
251 .iter()
252 .enumerate()
253 .map(|(edited_index, edited_value)| {
254 if let Some(baseline_index) = alignment[edited_index]
255 && let (Some(original_value), Some(baseline_value)) =
256 (original.get(baseline_index), baseline.get(baseline_index))
257 {
258 return patch_changed_json(original_value, baseline_value, edited_value);
259 }
260 Ok(edited_value.clone())
261 })
262 .collect()
263}
264
265fn patch_changed_json(original: &Json, baseline: &Json, edited: &Json) -> Result<Json> {
266 if baseline == edited {
267 return Ok(original.clone());
268 }
269
270 match (original, baseline, edited) {
271 (Json::Object(original), Json::Object(baseline), Json::Object(edited)) => {
272 if ["type", "role"]
273 .into_iter()
274 .any(|key| baseline.get(key) != edited.get(key))
275 {
276 return Ok(Json::Object(edited.clone()));
277 }
278 let mut patched = original.clone();
279 for key in baseline.keys().filter(|key| !edited.contains_key(*key)) {
280 patched.remove(key);
281 }
282 for (key, edited_value) in edited {
283 if baseline.get(key) == Some(edited_value) {
284 continue;
285 }
286 let value = match (original.get(key), baseline.get(key)) {
287 (Some(original_value), Some(baseline_value)) => {
288 patch_changed_json(original_value, baseline_value, edited_value)?
289 }
290 _ => edited_value.clone(),
291 };
292 patched.insert(key.clone(), value);
293 }
294 Ok(Json::Object(patched))
295 }
296 (Json::Array(original), Json::Array(baseline), Json::Array(edited)) => Ok(Json::Array(
297 patch_changed_array(original, baseline, edited)?,
298 )),
299 _ => Ok(edited.clone()),
300 }
301}
302
303#[cfg(test)]
304#[path = "../../tests/unit/codec/parity_tests.rs"]
305mod parity_tests;