1use std::collections::{BTreeMap, HashSet};
2use std::ops::Range;
3
4use zeroize::Zeroizing;
5
6#[cfg(test)]
7use std::cell::Cell;
8
9use gaze_types::inspection::{
10 ContentBlockIndexV1, JsonNodeOrdinalV1, JsonShapeNodeV1, JsonShapeSummaryV1,
11 JsonShapeTruncationCodeV1, JsonTypeCodeV1, ProjectionAvailabilityV1,
12 ProjectionOmissionReasonV1, SseDeltaKindV1, SseEntryOrdinalV1, SseEventKindV1,
13 SseTimelineEntryV1, SseTimelineMetaV1, MAX_JSON_SHAPE_NODES_V1,
14};
15
16use crate::codec::{
17 BodyCodec, CodecError, CodecErrorCode, CodecLimits, CodecPhase, InspectionParsedFactsV1,
18 InspectionStructuralProjectionV1, OpaqueCarrierScheme, OutputProvenance, ProvedRequestBody,
19 ProvedResponseBody, RequestTransformContext, ResponseTransformContext, WireFormat,
20};
21
22pub const ANTHROPIC_PROXY_PING_FRAME: &[u8] = b"event: ping\ndata: {\"type\":\"ping\"}\n\n";
23pub const ANTHROPIC_PROXY_ERROR_FRAME: &[u8] = b"event: error\ndata: {\"type\":\"error\",\"error\":{\"type\":\"api_error\",\"message\":\"proxy_validation_failed\"}}\n\n";
24const MAX_ANTHROPIC_REQUEST_BODY_BYTES: usize = 2 * 1024 * 1024;
25const MAX_ANGLE_WRAPPER_DEPTH: usize = 128;
27
28#[cfg(test)]
29#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
30pub(crate) struct InspectionConstructionCountsV1 {
31 pub(crate) json_sources_built: usize,
32 pub(crate) sse_sources_built: usize,
33 pub(crate) sse_timelines_built: usize,
34}
35
36#[cfg(test)]
37thread_local! {
38 static INSPECTION_CONSTRUCTION_COUNTS: Cell<InspectionConstructionCountsV1> =
39 const { Cell::new(InspectionConstructionCountsV1 {
40 json_sources_built: 0,
41 sse_sources_built: 0,
42 sse_timelines_built: 0,
43 }) };
44}
45
46#[cfg(test)]
47pub(crate) fn reset_inspection_construction_counts() {
48 INSPECTION_CONSTRUCTION_COUNTS.with(|counts| {
49 counts.set(InspectionConstructionCountsV1::default());
50 });
51}
52
53#[cfg(test)]
54pub(crate) fn inspection_construction_counts() -> InspectionConstructionCountsV1 {
55 INSPECTION_CONSTRUCTION_COUNTS.with(Cell::get)
56}
57
58#[cfg(test)]
59fn record_inspection_construction(update: impl FnOnce(&mut InspectionConstructionCountsV1)) {
60 INSPECTION_CONSTRUCTION_COUNTS.with(|cell| {
61 let mut counts = cell.get();
62 update(&mut counts);
63 cell.set(counts);
64 });
65}
66
67#[derive(Clone, Copy, Debug, Default)]
68pub struct AnthropicMessagesCodec;
69
70impl BodyCodec for AnthropicMessagesCodec {
71 fn protect_request(
72 &self,
73 input: &[u8],
74 ctx: &mut RequestTransformContext<'_>,
75 ) -> Result<ProvedRequestBody, CodecError> {
76 ctx.clear_opaque_carrier();
77 ctx.replace_content_block_index(None);
78 if ctx.format() != WireFormat::Json {
79 return Err(codec_error(
80 CodecErrorCode::InvalidWireFormat,
81 CodecPhase::RequestParse,
82 ));
83 }
84 if input.len()
85 > ctx
86 .limits()
87 .max_body_bytes()
88 .min(MAX_ANTHROPIC_REQUEST_BODY_BYTES)
89 {
90 return Err(codec_error(
91 CodecErrorCode::BodyLimitExceeded,
92 CodecPhase::RequestParse,
93 ));
94 }
95 let mut document = parse_json(input, ctx.limits(), CodecPhase::RequestParse)?;
96 let mut ledger = CoverageLedger::new(document.occurrence_count);
97 transform_request_root(&mut document.root, input, ctx, &mut ledger)
98 .map_err(|code| request_codec_error(code, CodecPhase::RequestProtection, ctx))?;
99 ledger
100 .finish()
101 .map_err(|code| codec_error(code, CodecPhase::RequestProof))?;
102 let expected_signed = collect_signed_slices(&document.root, input)
103 .map_err(|code| codec_error(code, CodecPhase::RequestProof))?;
104 let expected_authorized = collect_authorized_wire_chunks(&document.root)
105 .map_err(|code| codec_error(code, CodecPhase::RequestProof))?;
106 let (bytes, mut provenance) = write_document(&document, input)
107 .map_err(|code| codec_error(code, CodecPhase::RequestProof))?;
108 if bytes.len()
109 > ctx
110 .limits()
111 .max_body_bytes()
112 .min(MAX_ANTHROPIC_REQUEST_BODY_BYTES)
113 {
114 return Err(codec_error(
115 CodecErrorCode::BodyLimitExceeded,
116 CodecPhase::RequestProof,
117 ));
118 }
119 if !ctx
120 .limits()
121 .output_within_growth_limit(input.len(), bytes.len())
122 {
123 return Err(codec_error(
124 CodecErrorCode::GrowthLimitExceeded,
125 CodecPhase::RequestProof,
126 ));
127 }
128 let mut reparsed = parse_json(&bytes, ctx.limits(), CodecPhase::RequestProof)?;
129 let mut final_ledger = CoverageLedger::new(reparsed.occurrence_count);
130 transform_request_root(&mut reparsed.root, &bytes, ctx, &mut final_ledger)
131 .map_err(|code| request_codec_error(code, CodecPhase::RequestProof, ctx))?;
132 final_ledger
133 .finish()
134 .map_err(|code| codec_error(code, CodecPhase::RequestProof))?;
135 if !json_semantically_equal(&document.root, &reparsed.root) {
136 return Err(codec_error(
137 CodecErrorCode::InternalCoverageFailure,
138 CodecPhase::RequestProof,
139 ));
140 }
141 let (reproved, _) = write_document(&reparsed, &bytes)
142 .map_err(|code| codec_error(code, CodecPhase::RequestProof))?;
143 if reproved != bytes {
144 return Err(codec_error(
145 CodecErrorCode::InternalCoverageFailure,
146 CodecPhase::RequestProof,
147 ));
148 }
149 prove_request_logical_domains(&reparsed.root, reparsed.occurrence_count, ctx)
150 .map_err(|code| codec_error(code, CodecPhase::RequestProof))?;
151 provenance.set_occurrence_count(reparsed.occurrence_count);
152 verify_final_wire_proof(&bytes, &provenance, &expected_authorized, &expected_signed)
153 .map_err(|code| codec_error(code, CodecPhase::RequestProof))?;
154 provenance.mark_final_buffer_verified();
155 if ctx.inspection_projection_requested() && provenance.signed_opaque_ranges().is_empty() {
156 Ok(ProvedRequestBody::new_with_inspection_parsed_facts(
157 bytes,
158 WireFormat::Json,
159 provenance,
160 InspectionParsedFactsV1::AnthropicJson(JsonInspectionParsedFactsV1 {
161 root: reparsed.root,
162 }),
163 ))
164 } else {
165 Ok(ProvedRequestBody::new(bytes, WireFormat::Json, provenance))
166 }
167 }
168
169 fn restore_response(
170 &self,
171 input: &[u8],
172 ctx: &mut ResponseTransformContext<'_>,
173 ) -> Result<ProvedResponseBody, CodecError> {
174 if input.len() > ctx.limits().max_body_bytes() {
175 return Err(codec_error(
176 CodecErrorCode::BodyLimitExceeded,
177 CodecPhase::ResponseParse,
178 ));
179 }
180 match ctx.format() {
181 WireFormat::Json => restore_json_response(input, ctx),
182 WireFormat::Sse => self.restore_sse_chunks([input], ctx),
183 WireFormat::Empty if input.is_empty() => {
184 let mut provenance = OutputProvenance::default();
185 provenance.mark_final_buffer_verified();
186 Ok(ProvedResponseBody::new(
187 Vec::new(),
188 WireFormat::Empty,
189 provenance,
190 ))
191 }
192 WireFormat::Utf8Text => restore_text_response(input, ctx),
193 WireFormat::Ndjson => restore_ndjson_response(input, ctx),
194 WireFormat::Empty => Err(codec_error(
195 CodecErrorCode::InvalidWireFormat,
196 CodecPhase::ResponseParse,
197 )),
198 }
199 }
200}
201
202impl AnthropicMessagesCodec {
203 pub fn restore_sse_chunks<I, B>(
204 &self,
205 chunks: I,
206 ctx: &mut ResponseTransformContext<'_>,
207 ) -> Result<ProvedResponseBody, CodecError>
208 where
209 I: IntoIterator<Item = B>,
210 B: AsRef<[u8]>,
211 {
212 if ctx.format() != WireFormat::Sse {
213 return Err(codec_error(
214 CodecErrorCode::InvalidWireFormat,
215 CodecPhase::SseDecode,
216 ));
217 }
218 let mut decoder = SseDecoder::new(ctx.limits());
219 for chunk in chunks {
220 decoder.push(chunk.as_ref())?;
221 }
222 let input_len = decoder.input_len();
223 let frames = decoder.finish()?;
224 let proved = restore_sse_frames(frames, ctx)?;
225 if !ctx
226 .limits()
227 .output_within_growth_limit(input_len, proved.bytes().len())
228 {
229 return Err(codec_error(
230 CodecErrorCode::GrowthLimitExceeded,
231 CodecPhase::SseReplay,
232 ));
233 }
234 Ok(proved)
235 }
236}
237
238fn codec_error(code: CodecErrorCode, phase: CodecPhase) -> CodecError {
239 CodecError::new(code, phase)
240}
241
242fn request_codec_error(
243 code: CodecErrorCode,
244 phase: CodecPhase,
245 ctx: &RequestTransformContext<'_>,
246) -> CodecError {
247 codec_error(code, phase).with_opaque_carrier(
248 (code == CodecErrorCode::OpaqueMediaUninspected)
249 .then(|| ctx.opaque_carrier())
250 .flatten(),
251 )
252}
253
254fn restore_json_response(
255 input: &[u8],
256 ctx: &mut ResponseTransformContext<'_>,
257) -> Result<ProvedResponseBody, CodecError> {
258 let mut document = parse_json(input, ctx.limits(), CodecPhase::ResponseParse)?;
259 let mut ledger = CoverageLedger::new(document.occurrence_count);
260 transform_response_root(&mut document.root, input, ctx, &mut ledger)
261 .map_err(|code| codec_error(code, CodecPhase::ResponseRestore))?;
262 ledger
263 .finish()
264 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
265 validate_adjacent_response_text(&document.root, ctx)
266 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
267 let expected_signed = collect_signed_slices(&document.root, input)
268 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
269 let expected_authorized = collect_authorized_wire_chunks(&document.root)
270 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
271 let (bytes, mut provenance) = write_document(&document, input)
272 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
273 if !ctx
274 .limits()
275 .output_within_growth_limit(input.len(), bytes.len())
276 {
277 return Err(codec_error(
278 CodecErrorCode::GrowthLimitExceeded,
279 CodecPhase::ResponseProof,
280 ));
281 }
282 let reparsed = parse_json(&bytes, ctx.limits(), CodecPhase::ResponseProof)?;
283 let mut final_ledger = CoverageLedger::new(reparsed.occurrence_count);
284 account_subtree(&reparsed.root, &mut final_ledger)
285 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
286 final_ledger
287 .finish()
288 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
289 if !json_semantically_equal(&document.root, &reparsed.root) {
290 return Err(codec_error(
291 CodecErrorCode::InternalCoverageFailure,
292 CodecPhase::ResponseProof,
293 ));
294 }
295 let (reproved, _) = write_document(&reparsed, &bytes)
296 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
297 if reproved != bytes {
298 return Err(codec_error(
299 CodecErrorCode::InternalCoverageFailure,
300 CodecPhase::ResponseProof,
301 ));
302 }
303 provenance.set_occurrence_count(reparsed.occurrence_count);
304 verify_final_wire_proof(&bytes, &provenance, &expected_authorized, &expected_signed)
305 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
306 provenance.mark_final_buffer_verified();
307 if ctx.inspection_projection_requested() && provenance.signed_opaque_ranges().is_empty() {
308 Ok(ProvedResponseBody::new_with_inspection_parsed_facts(
309 bytes,
310 WireFormat::Json,
311 provenance,
312 InspectionParsedFactsV1::AnthropicJson(JsonInspectionParsedFactsV1 {
313 root: reparsed.root,
314 }),
315 ))
316 } else {
317 Ok(ProvedResponseBody::new(bytes, WireFormat::Json, provenance))
318 }
319}
320
321fn validate_adjacent_response_text(
322 root: &JsonNode,
323 ctx: &mut ResponseTransformContext<'_>,
324) -> Result<(), CodecErrorCode> {
325 let JsonKind::Object(root_members) = &root.kind else {
326 return Err(CodecErrorCode::UnsupportedResponseSurface);
327 };
328 let content = root_members
329 .iter()
330 .find(|member| member.key.value == "content")
331 .ok_or(CodecErrorCode::UnsupportedResponseSurface)?;
332 let JsonKind::Array(blocks) = &content.value.kind else {
333 return Err(CodecErrorCode::UnsupportedResponseSurface);
334 };
335 let mut logical = String::new();
336 let mut authorized = Vec::<Range<usize>>::new();
337 for block in blocks {
338 if object_string(block, "type")? != "text" {
339 continue;
340 }
341 let JsonKind::Object(members) = &block.kind else {
342 return Err(CodecErrorCode::UnsupportedResponseSurface);
343 };
344 let text = members
345 .iter()
346 .find(|member| member.key.value == "text")
347 .ok_or(CodecErrorCode::UnsupportedResponseSurface)?;
348 let JsonKind::String(value) = &text.value.kind else {
349 return Err(CodecErrorCode::UnsupportedResponseSurface);
350 };
351 let base = logical.len();
352 logical.push_str(&value.value);
353 authorized.extend(
354 value
355 .authorized
356 .iter()
357 .map(|range| base + range.start..base + range.end),
358 );
359 }
360 ctx.validate(&logical, &authorized)
361}
362
363fn restore_text_response(
364 input: &[u8],
365 ctx: &mut ResponseTransformContext<'_>,
366) -> Result<ProvedResponseBody, CodecError> {
367 let text = std::str::from_utf8(input)
368 .map_err(|_| codec_error(CodecErrorCode::InvalidUtf8, CodecPhase::ResponseParse))?;
369 let restored = ctx
370 .restore(text)
371 .map_err(|code| codec_error(code, CodecPhase::ResponseRestore))?;
372 guard_mutable_text_response(&restored.text)
373 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
374 ctx.validate(&restored.text, &restored.authorized_output_ranges)
375 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
376 if !ctx
377 .limits()
378 .output_within_growth_limit(input.len(), restored.text.len())
379 {
380 return Err(codec_error(
381 CodecErrorCode::GrowthLimitExceeded,
382 CodecPhase::ResponseProof,
383 ));
384 }
385 let output = restored.text.into_bytes();
386 let mut provenance = OutputProvenance::default();
387 let mut expected_authorized = Vec::new();
388 for range in restored.authorized_output_ranges {
389 expected_authorized.push(
390 output
391 .get(range.clone())
392 .ok_or_else(|| {
393 codec_error(
394 CodecErrorCode::InternalCoverageFailure,
395 CodecPhase::ResponseProof,
396 )
397 })?
398 .to_vec(),
399 );
400 provenance.record_authorized(range);
401 }
402 let final_text = std::str::from_utf8(&output)
403 .map_err(|_| codec_error(CodecErrorCode::InvalidUtf8, CodecPhase::ResponseProof))?;
404 guard_mutable_text_response(final_text)
405 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
406 ctx.validate(final_text, provenance.authorized_output_ranges())
407 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
408 verify_final_wire_proof(&output, &provenance, &expected_authorized, &[])
409 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
410 provenance.mark_final_buffer_verified();
411 Ok(ProvedResponseBody::new(
412 output,
413 WireFormat::Utf8Text,
414 provenance,
415 ))
416}
417
418fn restore_ndjson_response(
419 input: &[u8],
420 ctx: &mut ResponseTransformContext<'_>,
421) -> Result<ProvedResponseBody, CodecError> {
422 let text = std::str::from_utf8(input)
423 .map_err(|_| codec_error(CodecErrorCode::InvalidUtf8, CodecPhase::ResponseParse))?;
424 let mut output = Vec::with_capacity(input.len());
425 let mut provenance = OutputProvenance::default();
426 let mut expected_authorized = Vec::<Vec<u8>>::new();
427 let mut expected_signed = Vec::<Vec<u8>>::new();
428 let mut expected_authorized_ranges = Vec::<Range<usize>>::new();
429 let mut expected_signed_ranges = Vec::<Range<usize>>::new();
430 for line in text.split_inclusive('\n') {
431 let (body, newline) = line
432 .strip_suffix('\n')
433 .map_or((line, ""), |body| (body, "\n"));
434 if body.trim().is_empty() {
435 output.extend_from_slice(newline.as_bytes());
436 continue;
437 }
438 let proved = restore_json_response(body.as_bytes(), ctx)?;
439 let offset = output.len();
440 for range in proved.provenance().authorized_output_ranges() {
441 append_expected_wire_chunk(
442 proved.bytes(),
443 range,
444 offset,
445 &mut expected_authorized_ranges,
446 &mut expected_authorized,
447 )
448 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
449 }
450 for range in proved.provenance().signed_opaque_ranges() {
451 append_expected_wire_chunk(
452 proved.bytes(),
453 range,
454 offset,
455 &mut expected_signed_ranges,
456 &mut expected_signed,
457 )
458 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
459 }
460 output.extend_from_slice(proved.bytes());
461 for range in proved.provenance().authorized_output_ranges() {
462 provenance.record_authorized(offset + range.start..offset + range.end);
463 }
464 for range in proved.provenance().signed_opaque_ranges() {
465 provenance.record_signed(offset + range.start..offset + range.end);
466 }
467 output.extend_from_slice(newline.as_bytes());
468 }
469 if output.len() > ctx.limits().max_body_bytes() {
470 return Err(codec_error(
471 CodecErrorCode::BodyLimitExceeded,
472 CodecPhase::ResponseProof,
473 ));
474 }
475 if !ctx
476 .limits()
477 .output_within_growth_limit(input.len(), output.len())
478 {
479 return Err(codec_error(
480 CodecErrorCode::GrowthLimitExceeded,
481 CodecPhase::ResponseProof,
482 ));
483 }
484 let final_occurrence_count = reprove_ndjson_output(&output, ctx.limits())?;
485 provenance.set_occurrence_count(final_occurrence_count);
486 verify_final_wire_proof(&output, &provenance, &expected_authorized, &expected_signed)
487 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
488 provenance.mark_final_buffer_verified();
489 Ok(ProvedResponseBody::new(
490 output,
491 WireFormat::Ndjson,
492 provenance,
493 ))
494}
495
496fn reprove_ndjson_output(output: &[u8], limits: CodecLimits) -> Result<usize, CodecError> {
497 let text = std::str::from_utf8(output)
498 .map_err(|_| codec_error(CodecErrorCode::InvalidUtf8, CodecPhase::ResponseProof))?;
499 let mut occurrence_count = 0usize;
500 for line in text.split_inclusive('\n') {
501 let body = line.strip_suffix('\n').unwrap_or(line);
502 if body.trim().is_empty() {
503 continue;
504 }
505 let document = parse_json(body.as_bytes(), limits, CodecPhase::ResponseProof)?;
506 let mut ledger = CoverageLedger::new(document.occurrence_count);
507 account_subtree(&document.root, &mut ledger)
508 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
509 ledger
510 .finish()
511 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
512 prove_response_root_shape(&document.root)
513 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
514 let (reproved, _) = write_document(&document, body.as_bytes())
515 .map_err(|code| codec_error(code, CodecPhase::ResponseProof))?;
516 if reproved != body.as_bytes() {
517 return Err(codec_error(
518 CodecErrorCode::InternalCoverageFailure,
519 CodecPhase::ResponseProof,
520 ));
521 }
522 occurrence_count = occurrence_count
523 .checked_add(document.occurrence_count)
524 .ok_or_else(|| {
525 codec_error(CodecErrorCode::NodeLimitExceeded, CodecPhase::ResponseProof)
526 })?;
527 }
528 Ok(occurrence_count)
529}
530
531struct JsonDocument {
532 root: JsonNode,
533 occurrence_count: usize,
534}
535
536struct JsonNode {
537 occurrence: usize,
538 span: Range<usize>,
539 raw_preserve: bool,
540 kind: JsonKind,
541}
542
543enum JsonKind {
544 Object(Vec<JsonMember>),
545 Array(Vec<JsonNode>),
546 String(JsonString),
547 Number(String),
548 Bool(bool),
549 Null,
550}
551
552pub(crate) struct JsonInspectionParsedFactsV1 {
553 root: JsonNode,
554}
555
556pub(crate) struct SseInspectionParsedFactsV1 {
557 frames: Vec<SseFrame>,
558 complete: bool,
559}
560
561struct JsonInspectionProjectionSourceV1 {
562 root: JsonNode,
563}
564
565struct SseInspectionProjectionSourceV1 {
566 frames: Vec<SseFrame>,
567 complete: bool,
568}
569
570impl SseInspectionParsedFactsV1 {
571 pub(crate) fn project(self) -> InspectionStructuralProjectionV1 {
572 #[cfg(test)]
573 record_inspection_construction(|counts| counts.sse_sources_built += 1);
574 SseInspectionProjectionSourceV1 {
575 frames: self.frames,
576 complete: self.complete,
577 }
578 .project()
579 }
580}
581
582impl SseInspectionProjectionSourceV1 {
583 fn project(self) -> InspectionStructuralProjectionV1 {
584 #[cfg(test)]
585 record_inspection_construction(|counts| counts.sse_timelines_built += 1);
586 let entries = self
587 .complete
588 .then(|| {
589 self.frames
590 .into_iter()
591 .filter_map(|frame| frame.inspection_fact)
592 .enumerate()
593 .map(|(ordinal, fact)| {
594 let ordinal = u32::try_from(ordinal).ok()?;
595 Some(SseTimelineEntryV1::new(
596 SseEntryOrdinalV1::new(ordinal),
597 fact.event_kind,
598 fact.delta_kind,
599 fact.content_block,
600 ))
601 })
602 .collect::<Option<Vec<_>>>()
603 })
604 .flatten();
605 let sse_timeline = entries
606 .and_then(|entries| SseTimelineMetaV1::try_new(entries).ok())
607 .map_or_else(
608 || {
609 ProjectionAvailabilityV1::Omitted(
610 ProjectionOmissionReasonV1::ProjectionFailedClosed,
611 )
612 },
613 ProjectionAvailabilityV1::Present,
614 );
615 InspectionStructuralProjectionV1 {
616 json_shape: ProjectionAvailabilityV1::Omitted(
617 ProjectionOmissionReasonV1::UnsupportedFormat,
618 ),
619 sse_timeline,
620 }
621 }
622}
623
624impl JsonInspectionParsedFactsV1 {
625 pub(crate) fn project(self) -> InspectionStructuralProjectionV1 {
626 #[cfg(test)]
627 record_inspection_construction(|counts| counts.json_sources_built += 1);
628 JsonInspectionProjectionSourceV1 { root: self.root }.project()
629 }
630}
631
632impl JsonInspectionProjectionSourceV1 {
633 fn project(self) -> InspectionStructuralProjectionV1 {
634 let mut nodes = Vec::with_capacity(MAX_JSON_SHAPE_NODES_V1.min(64));
635 let mut truncated = false;
636 collect_json_shape(&self.root, &mut nodes, &mut truncated);
637 let truncation = if truncated {
638 JsonShapeTruncationCodeV1::NodeLimit
639 } else {
640 JsonShapeTruncationCodeV1::Complete
641 };
642 let json_shape = JsonShapeSummaryV1::try_new(nodes, truncation).map_or_else(
643 |_| {
644 ProjectionAvailabilityV1::Omitted(
645 ProjectionOmissionReasonV1::ProjectionFailedClosed,
646 )
647 },
648 ProjectionAvailabilityV1::Present,
649 );
650 InspectionStructuralProjectionV1 {
651 json_shape,
652 sse_timeline: ProjectionAvailabilityV1::Omitted(
653 ProjectionOmissionReasonV1::UnsupportedFormat,
654 ),
655 }
656 }
657}
658
659fn collect_json_shape(node: &JsonNode, output: &mut Vec<JsonShapeNodeV1>, truncated: &mut bool) {
660 if output.len() >= MAX_JSON_SHAPE_NODES_V1 {
661 *truncated = true;
662 return;
663 }
664 let ordinal = JsonNodeOrdinalV1::new(output.len() as u32);
665 let type_code = match &node.kind {
666 JsonKind::Object(_) => JsonTypeCodeV1::Object,
667 JsonKind::Array(_) => JsonTypeCodeV1::Array,
668 JsonKind::String(_) => JsonTypeCodeV1::String,
669 JsonKind::Number(_) => JsonTypeCodeV1::Number,
670 JsonKind::Bool(_) => JsonTypeCodeV1::Boolean,
671 JsonKind::Null => JsonTypeCodeV1::Null,
672 };
673 output.push(JsonShapeNodeV1::new(ordinal, type_code));
674 match &node.kind {
675 JsonKind::Object(members) => {
676 for member in members {
677 collect_json_shape(&member.value, output, truncated);
678 }
679 }
680 JsonKind::Array(values) => {
681 for value in values {
682 collect_json_shape(value, output, truncated);
683 }
684 }
685 JsonKind::String(_) | JsonKind::Number(_) | JsonKind::Bool(_) | JsonKind::Null => {}
686 }
687}
688
689struct JsonMember {
690 key_occurrence: usize,
691 key: JsonString,
692 value: JsonNode,
693}
694
695struct JsonString {
696 value: String,
697 authorized: Vec<Range<usize>>,
698}
699
700struct JsonParser<'a> {
701 bytes: &'a [u8],
702 position: usize,
703 limits: CodecLimits,
704 nodes: usize,
705 next_occurrence: usize,
706}
707
708fn parse_json(
709 input: &[u8],
710 limits: CodecLimits,
711 phase: CodecPhase,
712) -> Result<JsonDocument, CodecError> {
713 if input.len() > limits.max_body_bytes() {
714 return Err(codec_error(CodecErrorCode::BodyLimitExceeded, phase));
715 }
716 std::str::from_utf8(input).map_err(|_| codec_error(CodecErrorCode::InvalidUtf8, phase))?;
717 let mut parser = JsonParser {
718 bytes: input,
719 position: 0,
720 limits,
721 nodes: 0,
722 next_occurrence: 0,
723 };
724 parser.skip_whitespace();
725 let root = parser
726 .parse_value(0)
727 .map_err(|code| codec_error(code, phase))?;
728 parser.skip_whitespace();
729 if parser.position != input.len() {
730 return Err(codec_error(CodecErrorCode::InvalidJson, phase));
731 }
732 Ok(JsonDocument {
733 root,
734 occurrence_count: parser.next_occurrence,
735 })
736}
737
738impl JsonParser<'_> {
739 fn parse_value(&mut self, depth: usize) -> Result<JsonNode, CodecErrorCode> {
740 if depth > self.limits.max_json_depth() {
741 return Err(CodecErrorCode::DepthLimitExceeded);
742 }
743 self.nodes = self
744 .nodes
745 .checked_add(1)
746 .ok_or(CodecErrorCode::NodeLimitExceeded)?;
747 if self.nodes > self.limits.max_json_nodes() {
748 return Err(CodecErrorCode::NodeLimitExceeded);
749 }
750 let occurrence = self.occurrence();
751 let start = self.position;
752 let kind = match self.peek() {
753 Some(b'{') => self.parse_object(depth + 1)?,
754 Some(b'[') => self.parse_array(depth + 1)?,
755 Some(b'"') => JsonKind::String(self.parse_string()?),
756 Some(b't') => {
757 self.consume_literal(b"true")?;
758 JsonKind::Bool(true)
759 }
760 Some(b'f') => {
761 self.consume_literal(b"false")?;
762 JsonKind::Bool(false)
763 }
764 Some(b'n') => {
765 self.consume_literal(b"null")?;
766 JsonKind::Null
767 }
768 Some(b'-' | b'0'..=b'9') => JsonKind::Number(self.parse_number()?),
769 _ => return Err(CodecErrorCode::InvalidJson),
770 };
771 Ok(JsonNode {
772 occurrence,
773 span: start..self.position,
774 raw_preserve: false,
775 kind,
776 })
777 }
778
779 fn parse_object(&mut self, depth: usize) -> Result<JsonKind, CodecErrorCode> {
780 self.position += 1;
781 self.skip_whitespace();
782 let mut members = Vec::new();
783 let mut keys = HashSet::new();
784 if self.peek() == Some(b'}') {
785 self.position += 1;
786 return Ok(JsonKind::Object(members));
787 }
788 loop {
789 if self.peek() != Some(b'"') {
790 return Err(CodecErrorCode::InvalidJson);
791 }
792 let key_occurrence = self.occurrence();
793 let key = self.parse_string()?;
794 if !key.value.is_ascii() {
795 return Err(CodecErrorCode::InvalidJson);
796 }
797 if !keys.insert(key.value.clone()) {
798 return Err(CodecErrorCode::DuplicateObjectKey);
799 }
800 self.skip_whitespace();
801 if self.take() != Some(b':') {
802 return Err(CodecErrorCode::InvalidJson);
803 }
804 self.skip_whitespace();
805 let value = self.parse_value(depth)?;
806 members.push(JsonMember {
807 key_occurrence,
808 key,
809 value,
810 });
811 self.skip_whitespace();
812 match self.take() {
813 Some(b',') => self.skip_whitespace(),
814 Some(b'}') => break,
815 _ => return Err(CodecErrorCode::InvalidJson),
816 }
817 }
818 Ok(JsonKind::Object(members))
819 }
820
821 fn parse_array(&mut self, depth: usize) -> Result<JsonKind, CodecErrorCode> {
822 self.position += 1;
823 self.skip_whitespace();
824 let mut values = Vec::new();
825 if self.peek() == Some(b']') {
826 self.position += 1;
827 return Ok(JsonKind::Array(values));
828 }
829 loop {
830 values.push(self.parse_value(depth)?);
831 self.skip_whitespace();
832 match self.take() {
833 Some(b',') => self.skip_whitespace(),
834 Some(b']') => break,
835 _ => return Err(CodecErrorCode::InvalidJson),
836 }
837 }
838 Ok(JsonKind::Array(values))
839 }
840
841 fn parse_string(&mut self) -> Result<JsonString, CodecErrorCode> {
842 if self.take() != Some(b'"') {
843 return Err(CodecErrorCode::InvalidJson);
844 }
845 let mut value = String::new();
846 loop {
847 let byte = self.take().ok_or(CodecErrorCode::InvalidJson)?;
848 match byte {
849 b'"' => break,
850 b'\\' => self.parse_escape(&mut value)?,
851 0x00..=0x1f => return Err(CodecErrorCode::InvalidJson),
852 0x20..=0x7f => value.push(char::from(byte)),
853 _ => {
854 let start = self.position - 1;
855 let remaining = std::str::from_utf8(&self.bytes[start..])
856 .map_err(|_| CodecErrorCode::InvalidUtf8)?;
857 let character = remaining
858 .chars()
859 .next()
860 .ok_or(CodecErrorCode::InvalidUtf8)?;
861 value.push(character);
862 self.position = start + character.len_utf8();
863 }
864 }
865 if value.len() > self.limits.max_string_bytes() {
866 return Err(CodecErrorCode::StringLimitExceeded);
867 }
868 }
869 Ok(JsonString {
870 value,
871 authorized: Vec::new(),
872 })
873 }
874
875 fn parse_escape(&mut self, output: &mut String) -> Result<(), CodecErrorCode> {
876 match self.take().ok_or(CodecErrorCode::InvalidJson)? {
877 b'"' => output.push('"'),
878 b'\\' => output.push('\\'),
879 b'/' => output.push('/'),
880 b'b' => output.push('\u{0008}'),
881 b'f' => output.push('\u{000c}'),
882 b'n' => output.push('\n'),
883 b'r' => output.push('\r'),
884 b't' => output.push('\t'),
885 b'u' => {
886 let first = self.parse_hex_quad()?;
887 let scalar = if (0xd800..=0xdbff).contains(&first) {
888 if self.take() != Some(b'\\') || self.take() != Some(b'u') {
889 return Err(CodecErrorCode::InvalidJson);
890 }
891 let second = self.parse_hex_quad()?;
892 if !(0xdc00..=0xdfff).contains(&second) {
893 return Err(CodecErrorCode::InvalidJson);
894 }
895 0x1_0000 + (((u32::from(first) - 0xd800) << 10) | (u32::from(second) - 0xdc00))
896 } else if (0xdc00..=0xdfff).contains(&first) {
897 return Err(CodecErrorCode::InvalidJson);
898 } else {
899 u32::from(first)
900 };
901 output.push(char::from_u32(scalar).ok_or(CodecErrorCode::InvalidJson)?);
902 }
903 _ => return Err(CodecErrorCode::InvalidJson),
904 }
905 Ok(())
906 }
907
908 fn parse_hex_quad(&mut self) -> Result<u16, CodecErrorCode> {
909 let mut value = 0u16;
910 for _ in 0..4 {
911 let digit = self
912 .take()
913 .and_then(hex_value)
914 .ok_or(CodecErrorCode::InvalidJson)?;
915 value = (value << 4) | u16::from(digit);
916 }
917 Ok(value)
918 }
919
920 fn parse_number(&mut self) -> Result<String, CodecErrorCode> {
921 let start = self.position;
922 if self.peek() == Some(b'-') {
923 self.position += 1;
924 }
925 match self.take() {
926 Some(b'0') if matches!(self.peek(), Some(b'0'..=b'9')) => {
927 return Err(CodecErrorCode::InvalidJson);
928 }
929 Some(b'0'..=b'9') => {
930 while matches!(self.peek(), Some(b'0'..=b'9')) {
931 self.position += 1;
932 }
933 }
934 _ => return Err(CodecErrorCode::InvalidJson),
935 }
936 if self.peek() == Some(b'.') {
937 self.position += 1;
938 if !matches!(self.peek(), Some(b'0'..=b'9')) {
939 return Err(CodecErrorCode::InvalidJson);
940 }
941 while matches!(self.peek(), Some(b'0'..=b'9')) {
942 self.position += 1;
943 }
944 }
945 if matches!(self.peek(), Some(b'e' | b'E')) {
946 self.position += 1;
947 if matches!(self.peek(), Some(b'+' | b'-')) {
948 self.position += 1;
949 }
950 if !matches!(self.peek(), Some(b'0'..=b'9')) {
951 return Err(CodecErrorCode::InvalidJson);
952 }
953 while matches!(self.peek(), Some(b'0'..=b'9')) {
954 self.position += 1;
955 }
956 }
957 let raw = &self.bytes[start..self.position];
958 if raw.len() > self.limits.max_number_bytes() {
959 return Err(CodecErrorCode::NumberLimitExceeded);
960 }
961 Ok(std::str::from_utf8(raw)
962 .map_err(|_| CodecErrorCode::InvalidUtf8)?
963 .to_owned())
964 }
965
966 fn consume_literal(&mut self, literal: &[u8]) -> Result<(), CodecErrorCode> {
967 if self.bytes.get(self.position..self.position + literal.len()) != Some(literal) {
968 return Err(CodecErrorCode::InvalidJson);
969 }
970 self.position += literal.len();
971 Ok(())
972 }
973
974 fn occurrence(&mut self) -> usize {
975 let occurrence = self.next_occurrence;
976 self.next_occurrence += 1;
977 occurrence
978 }
979
980 fn skip_whitespace(&mut self) {
981 while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) {
982 self.position += 1;
983 }
984 }
985
986 fn peek(&self) -> Option<u8> {
987 self.bytes.get(self.position).copied()
988 }
989
990 fn take(&mut self) -> Option<u8> {
991 let value = self.peek()?;
992 self.position += 1;
993 Some(value)
994 }
995}
996
997fn hex_value(byte: u8) -> Option<u8> {
998 match byte {
999 b'0'..=b'9' => Some(byte - b'0'),
1000 b'a'..=b'f' => Some(byte - b'a' + 10),
1001 b'A'..=b'F' => Some(byte - b'A' + 10),
1002 _ => None,
1003 }
1004}
1005
1006struct CoverageLedger {
1007 visited: Vec<bool>,
1008}
1009
1010impl CoverageLedger {
1011 fn new(count: usize) -> Self {
1012 Self {
1013 visited: vec![false; count],
1014 }
1015 }
1016
1017 fn mark(&mut self, occurrence: usize) -> Result<(), CodecErrorCode> {
1018 let slot = self
1019 .visited
1020 .get_mut(occurrence)
1021 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
1022 if *slot {
1023 return Err(CodecErrorCode::InternalCoverageFailure);
1024 }
1025 *slot = true;
1026 Ok(())
1027 }
1028
1029 fn finish(self) -> Result<(), CodecErrorCode> {
1030 if self.visited.into_iter().all(|visited| visited) {
1031 Ok(())
1032 } else {
1033 Err(CodecErrorCode::InternalCoverageFailure)
1034 }
1035 }
1036}
1037
1038#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1039enum RequestCarrierClass {
1040 ReviewedConstant,
1041 RoutingControl,
1042 PromptMutable,
1043 PromptImmutable,
1044 MetadataMutable,
1045 SignedOpaque,
1046}
1047
1048#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1049enum PromptComponent {
1050 System,
1051 Tools,
1052 Messages,
1053}
1054
1055#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1056enum CarrierOrder {
1057 Semantic,
1058 Emitted,
1059}
1060
1061#[derive(Clone, Copy)]
1062enum SemanticObjectOrder {
1063 Closed(&'static [&'static str]),
1064 DecodedKeys,
1065}
1066
1067#[derive(Clone, Copy)]
1068struct FinalRequestCarrier<'a> {
1069 class: RequestCarrierClass,
1070 component: Option<PromptComponent>,
1071 text: &'a str,
1072}
1073
1074struct FinalRequestCarrierInventory<'a> {
1075 carriers: Vec<Option<FinalRequestCarrier<'a>>>,
1076 object_orders: Vec<Option<SemanticObjectOrder>>,
1077}
1078
1079impl<'a> FinalRequestCarrierInventory<'a> {
1080 fn new(occurrence_count: usize) -> Self {
1081 Self {
1082 carriers: vec![None; occurrence_count],
1083 object_orders: vec![None; occurrence_count],
1084 }
1085 }
1086
1087 fn classify(
1088 &mut self,
1089 occurrence: usize,
1090 text: &'a str,
1091 class: RequestCarrierClass,
1092 component: Option<PromptComponent>,
1093 ) -> Result<(), CodecErrorCode> {
1094 let slot = self
1095 .carriers
1096 .get_mut(occurrence)
1097 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
1098 if slot.is_some() {
1099 return Err(CodecErrorCode::InternalCoverageFailure);
1100 }
1101 *slot = Some(FinalRequestCarrier {
1102 class,
1103 component,
1104 text,
1105 });
1106 Ok(())
1107 }
1108
1109 fn set_object_order(
1110 &mut self,
1111 occurrence: usize,
1112 order: SemanticObjectOrder,
1113 ) -> Result<(), CodecErrorCode> {
1114 let slot = self
1115 .object_orders
1116 .get_mut(occurrence)
1117 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
1118 if slot.is_some() {
1119 return Err(CodecErrorCode::InternalCoverageFailure);
1120 }
1121 *slot = Some(order);
1122 Ok(())
1123 }
1124
1125 fn carrier(&self, occurrence: usize) -> Result<FinalRequestCarrier<'a>, CodecErrorCode> {
1126 self.carriers
1127 .get(occurrence)
1128 .and_then(Option::as_ref)
1129 .copied()
1130 .ok_or(CodecErrorCode::InternalCoverageFailure)
1131 }
1132
1133 fn object_order(&self, occurrence: usize) -> Result<SemanticObjectOrder, CodecErrorCode> {
1134 self.object_orders
1135 .get(occurrence)
1136 .and_then(Option::as_ref)
1137 .copied()
1138 .ok_or(CodecErrorCode::InternalCoverageFailure)
1139 }
1140
1141 fn prompt_component_present(&self, component: PromptComponent) -> bool {
1142 self.carriers.iter().flatten().any(|carrier| {
1143 carrier.component == Some(component)
1144 && matches!(
1145 carrier.class,
1146 RequestCarrierClass::PromptMutable | RequestCarrierClass::PromptImmutable
1147 )
1148 })
1149 }
1150
1151 fn metadata_present(&self) -> bool {
1152 self.carriers
1153 .iter()
1154 .flatten()
1155 .any(|carrier| carrier.class == RequestCarrierClass::MetadataMutable)
1156 }
1157
1158 fn validate_complete(&self, root: &JsonNode) -> Result<(), CodecErrorCode> {
1159 let mut expected_carriers = vec![false; self.carriers.len()];
1160 let mut expected_objects = vec![false; self.object_orders.len()];
1161 collect_inventory_expectations(root, &mut expected_carriers, &mut expected_objects)?;
1162 if expected_carriers
1163 .iter()
1164 .zip(&self.carriers)
1165 .any(|(expected, carrier)| *expected != carrier.is_some())
1166 || expected_objects
1167 .iter()
1168 .zip(&self.object_orders)
1169 .any(|(expected, order)| *expected != order.is_some())
1170 {
1171 return Err(CodecErrorCode::InternalCoverageFailure);
1172 }
1173 Ok(())
1174 }
1175}
1176
1177const ROOT_SEMANTIC_FIELDS: &[&str] = &[
1178 "system",
1179 "tools",
1180 "messages",
1181 "metadata",
1182 "model",
1183 "service_tier",
1184 "max_tokens",
1185 "top_k",
1186 "temperature",
1187 "top_p",
1188 "stream",
1189 "stop_sequences",
1190 "thinking",
1191 "tool_choice",
1192 "cache_control",
1193];
1194const MESSAGE_SEMANTIC_FIELDS: &[&str] = &["role", "content"];
1195const TEXT_BLOCK_SEMANTIC_FIELDS: &[&str] = &["type", "text", "cache_control"];
1196const TOOL_USE_BLOCK_SEMANTIC_FIELDS: &[&str] = &["type", "id", "name", "input", "cache_control"];
1197const TOOL_RESULT_BLOCK_SEMANTIC_FIELDS: &[&str] = &[
1198 "type",
1199 "tool_use_id",
1200 "content",
1201 "is_error",
1202 "cache_control",
1203];
1204const DOCUMENT_BLOCK_SEMANTIC_FIELDS: &[&str] =
1205 &["type", "source", "title", "context", "cache_control"];
1206const SEARCH_BLOCK_SEMANTIC_FIELDS: &[&str] = &[
1207 "type",
1208 "title",
1209 "context",
1210 "text",
1211 "content",
1212 "cache_control",
1213];
1214const THINKING_BLOCK_SEMANTIC_FIELDS: &[&str] = &["type", "thinking", "signature", "cache_control"];
1215const REDACTED_THINKING_BLOCK_SEMANTIC_FIELDS: &[&str] = &["type", "data", "cache_control"];
1216const DOCUMENT_SOURCE_SEMANTIC_FIELDS: &[&str] = &["type", "media_type", "data", "text", "content"];
1217const TOOL_SEMANTIC_FIELDS: &[&str] = &[
1218 "name",
1219 "type",
1220 "description",
1221 "input_schema",
1222 "cache_control",
1223];
1224const SCHEMA_SEMANTIC_FIELDS: &[&str] = &[
1225 "type",
1226 "title",
1227 "description",
1228 "properties",
1229 "required",
1230 "$defs",
1231 "items",
1232 "additionalProperties",
1233 "anyOf",
1234 "allOf",
1235 "oneOf",
1236 "examples",
1237 "default",
1238 "const",
1239 "enum",
1240 "$ref",
1241 "$id",
1242 "$schema",
1243 "format",
1244 "pattern",
1245 "minimum",
1246 "maximum",
1247];
1248const CACHE_CONTROL_SEMANTIC_FIELDS: &[&str] = &["type", "ttl"];
1249const THINKING_CONTROL_SEMANTIC_FIELDS: &[&str] = &["type", "budget_tokens"];
1250const TOOL_CHOICE_SEMANTIC_FIELDS: &[&str] = &["type", "name", "disable_parallel_tool_use"];
1251
1252fn collect_inventory_expectations(
1253 node: &JsonNode,
1254 carriers: &mut [bool],
1255 objects: &mut [bool],
1256) -> Result<(), CodecErrorCode> {
1257 let mark = |slots: &mut [bool], occurrence: usize| -> Result<(), CodecErrorCode> {
1258 let slot = slots
1259 .get_mut(occurrence)
1260 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
1261 if *slot {
1262 return Err(CodecErrorCode::InternalCoverageFailure);
1263 }
1264 *slot = true;
1265 Ok(())
1266 };
1267 match &node.kind {
1268 JsonKind::Object(members) => {
1269 mark(objects, node.occurrence)?;
1270 for member in members {
1271 mark(carriers, member.key_occurrence)?;
1272 collect_inventory_expectations(&member.value, carriers, objects)?;
1273 }
1274 }
1275 JsonKind::Array(values) => {
1276 for value in values {
1277 collect_inventory_expectations(value, carriers, objects)?;
1278 }
1279 }
1280 JsonKind::String(_) => mark(carriers, node.occurrence)?,
1281 JsonKind::Number(_) | JsonKind::Bool(_) | JsonKind::Null => {}
1282 }
1283 Ok(())
1284}
1285
1286fn collect_final_request_carriers<'a>(
1287 root: &'a JsonNode,
1288 occurrence_count: usize,
1289) -> Result<FinalRequestCarrierInventory<'a>, CodecErrorCode> {
1290 let mut inventory = FinalRequestCarrierInventory::new(occurrence_count);
1291 classify_request_root(root, &mut inventory)?;
1292 inventory.validate_complete(root)?;
1293 Ok(inventory)
1294}
1295
1296fn classify_request_root<'a>(
1297 node: &'a JsonNode,
1298 inventory: &mut FinalRequestCarrierInventory<'a>,
1299) -> Result<(), CodecErrorCode> {
1300 let members = object_members(node)?;
1301 inventory.set_object_order(
1302 node.occurrence,
1303 SemanticObjectOrder::Closed(ROOT_SEMANTIC_FIELDS),
1304 )?;
1305 for member in members {
1306 classify_key(
1307 member,
1308 RequestCarrierClass::ReviewedConstant,
1309 None,
1310 inventory,
1311 )?;
1312 match member.key.value.as_str() {
1313 "model" => classify_string_node(
1314 &member.value,
1315 RequestCarrierClass::RoutingControl,
1316 None,
1317 inventory,
1318 )?,
1319 "service_tier" => classify_string_node(
1320 &member.value,
1321 RequestCarrierClass::ReviewedConstant,
1322 None,
1323 inventory,
1324 )?,
1325 "stop_sequences" => classify_string_array(
1326 &member.value,
1327 RequestCarrierClass::RoutingControl,
1328 None,
1329 inventory,
1330 )?,
1331 "system" => classify_request_system(&member.value, inventory)?,
1332 "messages" => classify_request_messages(&member.value, inventory)?,
1333 "tools" => classify_request_tools(&member.value, inventory)?,
1334 "metadata" => classify_arbitrary_value(
1335 &member.value,
1336 RequestCarrierClass::MetadataMutable,
1337 None,
1338 inventory,
1339 )?,
1340 "thinking" => classify_thinking_control(&member.value, inventory)?,
1341 "tool_choice" => classify_tool_choice_control(&member.value, inventory)?,
1342 "cache_control" => classify_cache_control(&member.value, inventory)?,
1343 "max_tokens" | "top_k" | "temperature" | "top_p" | "stream" => {}
1344 _ => return Err(CodecErrorCode::InternalCoverageFailure),
1345 }
1346 }
1347 Ok(())
1348}
1349
1350fn classify_request_system<'a>(
1351 node: &'a JsonNode,
1352 inventory: &mut FinalRequestCarrierInventory<'a>,
1353) -> Result<(), CodecErrorCode> {
1354 match &node.kind {
1355 JsonKind::String(_) => classify_string_node(
1356 node,
1357 RequestCarrierClass::PromptMutable,
1358 Some(PromptComponent::System),
1359 inventory,
1360 ),
1361 JsonKind::Array(blocks) => {
1362 for block in blocks {
1363 classify_request_block(block, PromptComponent::System, inventory)?;
1364 }
1365 Ok(())
1366 }
1367 _ => Err(CodecErrorCode::InternalCoverageFailure),
1368 }
1369}
1370
1371fn classify_request_messages<'a>(
1372 node: &'a JsonNode,
1373 inventory: &mut FinalRequestCarrierInventory<'a>,
1374) -> Result<(), CodecErrorCode> {
1375 let JsonKind::Array(messages) = &node.kind else {
1376 return Err(CodecErrorCode::InternalCoverageFailure);
1377 };
1378 for message in messages {
1379 let members = object_members(message)?;
1380 inventory.set_object_order(
1381 message.occurrence,
1382 SemanticObjectOrder::Closed(MESSAGE_SEMANTIC_FIELDS),
1383 )?;
1384 for member in members {
1385 classify_key(
1386 member,
1387 RequestCarrierClass::ReviewedConstant,
1388 None,
1389 inventory,
1390 )?;
1391 match member.key.value.as_str() {
1392 "role" => classify_string_node(
1393 &member.value,
1394 RequestCarrierClass::ReviewedConstant,
1395 None,
1396 inventory,
1397 )?,
1398 "content" => {
1399 classify_request_content(&member.value, PromptComponent::Messages, inventory)?
1400 }
1401 _ => return Err(CodecErrorCode::InternalCoverageFailure),
1402 }
1403 }
1404 }
1405 Ok(())
1406}
1407
1408fn classify_request_content<'a>(
1409 node: &'a JsonNode,
1410 component: PromptComponent,
1411 inventory: &mut FinalRequestCarrierInventory<'a>,
1412) -> Result<(), CodecErrorCode> {
1413 match &node.kind {
1414 JsonKind::String(_) => classify_string_node(
1415 node,
1416 RequestCarrierClass::PromptMutable,
1417 Some(component),
1418 inventory,
1419 ),
1420 JsonKind::Array(blocks) => {
1421 for block in blocks {
1422 classify_request_block(block, component, inventory)?;
1423 }
1424 Ok(())
1425 }
1426 _ => Err(CodecErrorCode::InternalCoverageFailure),
1427 }
1428}
1429
1430fn classify_request_block<'a>(
1431 node: &'a JsonNode,
1432 component: PromptComponent,
1433 inventory: &mut FinalRequestCarrierInventory<'a>,
1434) -> Result<(), CodecErrorCode> {
1435 let block_type = object_string(node, "type")?;
1436 let order = match block_type {
1437 "text" => TEXT_BLOCK_SEMANTIC_FIELDS,
1438 "tool_use" => TOOL_USE_BLOCK_SEMANTIC_FIELDS,
1439 "tool_result" => TOOL_RESULT_BLOCK_SEMANTIC_FIELDS,
1440 "document" => DOCUMENT_BLOCK_SEMANTIC_FIELDS,
1441 "search_result" | "custom_content" => SEARCH_BLOCK_SEMANTIC_FIELDS,
1442 "thinking" => THINKING_BLOCK_SEMANTIC_FIELDS,
1443 "redacted_thinking" => REDACTED_THINKING_BLOCK_SEMANTIC_FIELDS,
1444 _ => return Err(CodecErrorCode::InternalCoverageFailure),
1445 };
1446 let members = object_members(node)?;
1447 inventory.set_object_order(node.occurrence, SemanticObjectOrder::Closed(order))?;
1448 for member in members {
1449 classify_key(
1450 member,
1451 RequestCarrierClass::ReviewedConstant,
1452 None,
1453 inventory,
1454 )?;
1455 match (block_type, member.key.value.as_str()) {
1456 (_, "type") => classify_string_node(
1457 &member.value,
1458 RequestCarrierClass::ReviewedConstant,
1459 None,
1460 inventory,
1461 )?,
1462 (_, "cache_control") => classify_cache_control(&member.value, inventory)?,
1463 ("text", "text") => classify_string_node(
1464 &member.value,
1465 RequestCarrierClass::PromptMutable,
1466 Some(component),
1467 inventory,
1468 )?,
1469 ("tool_use", "id" | "name") | ("tool_result", "tool_use_id") => classify_string_node(
1470 &member.value,
1471 RequestCarrierClass::PromptImmutable,
1472 Some(component),
1473 inventory,
1474 )?,
1475 ("tool_use", "input") => classify_arbitrary_value(
1476 &member.value,
1477 RequestCarrierClass::PromptMutable,
1478 Some(component),
1479 inventory,
1480 )?,
1481 ("tool_result", "content") => {
1482 classify_request_content(&member.value, component, inventory)?
1483 }
1484 ("tool_result", "is_error") => {}
1485 ("document", "source") => {
1486 classify_document_source(&member.value, component, inventory)?
1487 }
1488 ("document", "title" | "context")
1489 | ("search_result" | "custom_content", "title" | "context" | "text" | "content") => {
1490 classify_arbitrary_value(
1491 &member.value,
1492 RequestCarrierClass::PromptMutable,
1493 Some(component),
1494 inventory,
1495 )?
1496 }
1497 ("thinking", "thinking") => classify_string_node(
1498 &member.value,
1499 RequestCarrierClass::PromptImmutable,
1500 Some(component),
1501 inventory,
1502 )?,
1503 ("thinking", "signature") | ("redacted_thinking", "data") => classify_string_node(
1504 &member.value,
1505 RequestCarrierClass::SignedOpaque,
1506 None,
1507 inventory,
1508 )?,
1509 _ => return Err(CodecErrorCode::InternalCoverageFailure),
1510 }
1511 }
1512 Ok(())
1513}
1514
1515fn classify_document_source<'a>(
1516 node: &'a JsonNode,
1517 component: PromptComponent,
1518 inventory: &mut FinalRequestCarrierInventory<'a>,
1519) -> Result<(), CodecErrorCode> {
1520 let members = object_members(node)?;
1521 inventory.set_object_order(
1522 node.occurrence,
1523 SemanticObjectOrder::Closed(DOCUMENT_SOURCE_SEMANTIC_FIELDS),
1524 )?;
1525 for member in members {
1526 classify_key(
1527 member,
1528 RequestCarrierClass::ReviewedConstant,
1529 None,
1530 inventory,
1531 )?;
1532 match member.key.value.as_str() {
1533 "type" => classify_string_node(
1534 &member.value,
1535 RequestCarrierClass::ReviewedConstant,
1536 None,
1537 inventory,
1538 )?,
1539 "media_type" => classify_string_node(
1540 &member.value,
1541 RequestCarrierClass::PromptImmutable,
1542 Some(component),
1543 inventory,
1544 )?,
1545 "data" | "text" | "content" => classify_arbitrary_value(
1546 &member.value,
1547 RequestCarrierClass::PromptMutable,
1548 Some(component),
1549 inventory,
1550 )?,
1551 _ => return Err(CodecErrorCode::InternalCoverageFailure),
1552 }
1553 }
1554 Ok(())
1555}
1556
1557fn classify_request_tools<'a>(
1558 node: &'a JsonNode,
1559 inventory: &mut FinalRequestCarrierInventory<'a>,
1560) -> Result<(), CodecErrorCode> {
1561 let JsonKind::Array(tools) = &node.kind else {
1562 return Err(CodecErrorCode::InternalCoverageFailure);
1563 };
1564 for tool in tools {
1565 let members = object_members(tool)?;
1566 inventory.set_object_order(
1567 tool.occurrence,
1568 SemanticObjectOrder::Closed(TOOL_SEMANTIC_FIELDS),
1569 )?;
1570 for member in members {
1571 classify_key(
1572 member,
1573 RequestCarrierClass::ReviewedConstant,
1574 None,
1575 inventory,
1576 )?;
1577 match member.key.value.as_str() {
1578 "name" => classify_string_node(
1579 &member.value,
1580 RequestCarrierClass::PromptImmutable,
1581 Some(PromptComponent::Tools),
1582 inventory,
1583 )?,
1584 "type" => classify_string_node(
1585 &member.value,
1586 RequestCarrierClass::ReviewedConstant,
1587 None,
1588 inventory,
1589 )?,
1590 "description" => classify_string_node(
1591 &member.value,
1592 RequestCarrierClass::PromptMutable,
1593 Some(PromptComponent::Tools),
1594 inventory,
1595 )?,
1596 "input_schema" => classify_request_schema(&member.value, inventory)?,
1597 "cache_control" => classify_cache_control(&member.value, inventory)?,
1598 _ => return Err(CodecErrorCode::InternalCoverageFailure),
1599 }
1600 }
1601 }
1602 Ok(())
1603}
1604
1605fn classify_request_schema<'a>(
1606 node: &'a JsonNode,
1607 inventory: &mut FinalRequestCarrierInventory<'a>,
1608) -> Result<(), CodecErrorCode> {
1609 let members = object_members(node)?;
1610 inventory.set_object_order(
1611 node.occurrence,
1612 SemanticObjectOrder::Closed(SCHEMA_SEMANTIC_FIELDS),
1613 )?;
1614 for member in members {
1615 classify_key(
1616 member,
1617 RequestCarrierClass::ReviewedConstant,
1618 None,
1619 inventory,
1620 )?;
1621 match member.key.value.as_str() {
1622 "description" | "title" | "examples" | "default" => classify_arbitrary_value(
1623 &member.value,
1624 RequestCarrierClass::PromptMutable,
1625 Some(PromptComponent::Tools),
1626 inventory,
1627 )?,
1628 "properties" => {
1629 classify_schema_map(&member.value, RequestCarrierClass::PromptMutable, inventory)?
1630 }
1631 "$defs" => classify_schema_map(
1632 &member.value,
1633 RequestCarrierClass::PromptImmutable,
1634 inventory,
1635 )?,
1636 "items" => classify_request_schema(&member.value, inventory)?,
1637 "additionalProperties" => {
1638 if matches!(member.value.kind, JsonKind::Object(_)) {
1639 classify_request_schema(&member.value, inventory)?;
1640 }
1641 }
1642 "anyOf" | "allOf" | "oneOf" => {
1643 let JsonKind::Array(values) = &member.value.kind else {
1644 return Err(CodecErrorCode::InternalCoverageFailure);
1645 };
1646 for value in values {
1647 classify_request_schema(value, inventory)?;
1648 }
1649 }
1650 "required" => classify_string_array(
1651 &member.value,
1652 RequestCarrierClass::PromptImmutable,
1653 Some(PromptComponent::Tools),
1654 inventory,
1655 )?,
1656 "type" => classify_schema_type(&member.value, inventory)?,
1657 "$ref" | "$id" | "$schema" | "format" | "pattern" => classify_string_node(
1658 &member.value,
1659 RequestCarrierClass::PromptImmutable,
1660 Some(PromptComponent::Tools),
1661 inventory,
1662 )?,
1663 "const" => classify_schema_literal(&member.value, inventory)?,
1664 "enum" => {
1665 let JsonKind::Array(values) = &member.value.kind else {
1666 return Err(CodecErrorCode::InternalCoverageFailure);
1667 };
1668 for value in values {
1669 classify_schema_literal(value, inventory)?;
1670 }
1671 }
1672 "minimum" | "maximum" => {}
1673 _ => return Err(CodecErrorCode::InternalCoverageFailure),
1674 }
1675 }
1676 Ok(())
1677}
1678
1679fn classify_schema_map<'a>(
1680 node: &'a JsonNode,
1681 key_class: RequestCarrierClass,
1682 inventory: &mut FinalRequestCarrierInventory<'a>,
1683) -> Result<(), CodecErrorCode> {
1684 let members = object_members(node)?;
1685 inventory.set_object_order(node.occurrence, SemanticObjectOrder::DecodedKeys)?;
1686 for member in members {
1687 classify_key(member, key_class, Some(PromptComponent::Tools), inventory)?;
1688 classify_request_schema(&member.value, inventory)?;
1689 }
1690 Ok(())
1691}
1692
1693fn classify_schema_type<'a>(
1694 node: &'a JsonNode,
1695 inventory: &mut FinalRequestCarrierInventory<'a>,
1696) -> Result<(), CodecErrorCode> {
1697 match &node.kind {
1698 JsonKind::String(_) => {
1699 classify_string_node(node, RequestCarrierClass::ReviewedConstant, None, inventory)
1700 }
1701 JsonKind::Array(values) => {
1702 for value in values {
1703 classify_string_node(
1704 value,
1705 RequestCarrierClass::ReviewedConstant,
1706 None,
1707 inventory,
1708 )?;
1709 }
1710 Ok(())
1711 }
1712 _ => Err(CodecErrorCode::InternalCoverageFailure),
1713 }
1714}
1715
1716fn classify_schema_literal<'a>(
1717 node: &'a JsonNode,
1718 inventory: &mut FinalRequestCarrierInventory<'a>,
1719) -> Result<(), CodecErrorCode> {
1720 if matches!(node.kind, JsonKind::String(_)) {
1721 classify_string_node(
1722 node,
1723 RequestCarrierClass::PromptImmutable,
1724 Some(PromptComponent::Tools),
1725 inventory,
1726 )?;
1727 }
1728 Ok(())
1729}
1730
1731fn classify_arbitrary_value<'a>(
1732 node: &'a JsonNode,
1733 class: RequestCarrierClass,
1734 component: Option<PromptComponent>,
1735 inventory: &mut FinalRequestCarrierInventory<'a>,
1736) -> Result<(), CodecErrorCode> {
1737 match &node.kind {
1738 JsonKind::Object(members) => {
1739 inventory.set_object_order(node.occurrence, SemanticObjectOrder::DecodedKeys)?;
1740 for member in members {
1741 classify_key(member, class, component, inventory)?;
1742 classify_arbitrary_value(&member.value, class, component, inventory)?;
1743 }
1744 Ok(())
1745 }
1746 JsonKind::Array(values) => {
1747 for value in values {
1748 classify_arbitrary_value(value, class, component, inventory)?;
1749 }
1750 Ok(())
1751 }
1752 JsonKind::String(_) => classify_string_node(node, class, component, inventory),
1753 JsonKind::Bool(_) | JsonKind::Null => Ok(()),
1754 JsonKind::Number(_) => Err(CodecErrorCode::InternalCoverageFailure),
1755 }
1756}
1757
1758fn classify_cache_control<'a>(
1759 node: &'a JsonNode,
1760 inventory: &mut FinalRequestCarrierInventory<'a>,
1761) -> Result<(), CodecErrorCode> {
1762 classify_closed_constant_object(node, CACHE_CONTROL_SEMANTIC_FIELDS, inventory)
1763}
1764
1765fn classify_thinking_control<'a>(
1766 node: &'a JsonNode,
1767 inventory: &mut FinalRequestCarrierInventory<'a>,
1768) -> Result<(), CodecErrorCode> {
1769 classify_closed_constant_object(node, THINKING_CONTROL_SEMANTIC_FIELDS, inventory)
1770}
1771
1772fn classify_tool_choice_control<'a>(
1773 node: &'a JsonNode,
1774 inventory: &mut FinalRequestCarrierInventory<'a>,
1775) -> Result<(), CodecErrorCode> {
1776 let members = object_members(node)?;
1777 inventory.set_object_order(
1778 node.occurrence,
1779 SemanticObjectOrder::Closed(TOOL_CHOICE_SEMANTIC_FIELDS),
1780 )?;
1781 for member in members {
1782 classify_key(
1783 member,
1784 RequestCarrierClass::ReviewedConstant,
1785 None,
1786 inventory,
1787 )?;
1788 match member.key.value.as_str() {
1789 "type" => classify_string_node(
1790 &member.value,
1791 RequestCarrierClass::ReviewedConstant,
1792 None,
1793 inventory,
1794 )?,
1795 "name" => classify_string_node(
1796 &member.value,
1797 RequestCarrierClass::RoutingControl,
1798 None,
1799 inventory,
1800 )?,
1801 "disable_parallel_tool_use" => {}
1802 _ => return Err(CodecErrorCode::InternalCoverageFailure),
1803 }
1804 }
1805 Ok(())
1806}
1807
1808fn classify_closed_constant_object<'a>(
1809 node: &'a JsonNode,
1810 order: &'static [&'static str],
1811 inventory: &mut FinalRequestCarrierInventory<'a>,
1812) -> Result<(), CodecErrorCode> {
1813 let members = object_members(node)?;
1814 inventory.set_object_order(node.occurrence, SemanticObjectOrder::Closed(order))?;
1815 for member in members {
1816 classify_key(
1817 member,
1818 RequestCarrierClass::ReviewedConstant,
1819 None,
1820 inventory,
1821 )?;
1822 if matches!(member.value.kind, JsonKind::String(_)) {
1823 classify_string_node(
1824 &member.value,
1825 RequestCarrierClass::ReviewedConstant,
1826 None,
1827 inventory,
1828 )?;
1829 }
1830 }
1831 Ok(())
1832}
1833
1834fn classify_string_array<'a>(
1835 node: &'a JsonNode,
1836 class: RequestCarrierClass,
1837 component: Option<PromptComponent>,
1838 inventory: &mut FinalRequestCarrierInventory<'a>,
1839) -> Result<(), CodecErrorCode> {
1840 let JsonKind::Array(values) = &node.kind else {
1841 return Err(CodecErrorCode::InternalCoverageFailure);
1842 };
1843 for value in values {
1844 classify_string_node(value, class, component, inventory)?;
1845 }
1846 Ok(())
1847}
1848
1849fn classify_key<'a>(
1850 member: &'a JsonMember,
1851 class: RequestCarrierClass,
1852 component: Option<PromptComponent>,
1853 inventory: &mut FinalRequestCarrierInventory<'a>,
1854) -> Result<(), CodecErrorCode> {
1855 inventory.classify(member.key_occurrence, &member.key.value, class, component)
1856}
1857
1858fn classify_string_node<'a>(
1859 node: &'a JsonNode,
1860 class: RequestCarrierClass,
1861 component: Option<PromptComponent>,
1862 inventory: &mut FinalRequestCarrierInventory<'a>,
1863) -> Result<(), CodecErrorCode> {
1864 let JsonKind::String(value) = &node.kind else {
1865 return Err(CodecErrorCode::InternalCoverageFailure);
1866 };
1867 inventory.classify(node.occurrence, &value.value, class, component)
1868}
1869
1870fn object_members(node: &JsonNode) -> Result<&[JsonMember], CodecErrorCode> {
1871 let JsonKind::Object(members) = &node.kind else {
1872 return Err(CodecErrorCode::InternalCoverageFailure);
1873 };
1874 Ok(members)
1875}
1876
1877#[derive(Clone, Copy)]
1878enum RequestViewSelection {
1879 Prompt(PromptComponent),
1880 Metadata,
1881}
1882
1883fn build_prompt_component_view(
1884 root: &JsonNode,
1885 inventory: &FinalRequestCarrierInventory<'_>,
1886 component: PromptComponent,
1887 order: CarrierOrder,
1888 limit: usize,
1889) -> Result<Zeroizing<String>, CodecErrorCode> {
1890 let mut output = Zeroizing::new(String::new());
1891 append_request_view(
1892 root,
1893 inventory,
1894 RequestViewSelection::Prompt(component),
1895 order,
1896 limit,
1897 &mut output,
1898 )?;
1899 Ok(output)
1900}
1901
1902fn build_metadata_view(
1903 root: &JsonNode,
1904 inventory: &FinalRequestCarrierInventory<'_>,
1905 order: CarrierOrder,
1906 limit: usize,
1907) -> Result<Zeroizing<String>, CodecErrorCode> {
1908 let mut output = Zeroizing::new(String::new());
1909 append_request_view(
1910 root,
1911 inventory,
1912 RequestViewSelection::Metadata,
1913 order,
1914 limit,
1915 &mut output,
1916 )?;
1917 Ok(output)
1918}
1919
1920fn append_request_view(
1921 node: &JsonNode,
1922 inventory: &FinalRequestCarrierInventory<'_>,
1923 selection: RequestViewSelection,
1924 order: CarrierOrder,
1925 limit: usize,
1926 output: &mut String,
1927) -> Result<(), CodecErrorCode> {
1928 match &node.kind {
1929 JsonKind::Object(members) => match order {
1930 CarrierOrder::Emitted => {
1931 for member in members {
1932 append_inventory_carrier(
1933 member.key_occurrence,
1934 inventory,
1935 selection,
1936 limit,
1937 output,
1938 )?;
1939 append_request_view(&member.value, inventory, selection, order, limit, output)?;
1940 }
1941 }
1942 CarrierOrder::Semantic => {
1943 let mut ordered = Vec::new();
1944 ordered
1945 .try_reserve(members.len())
1946 .map_err(|_| CodecErrorCode::BodyLimitExceeded)?;
1947 ordered.extend(members);
1948 match inventory.object_order(node.occurrence)? {
1949 SemanticObjectOrder::Closed(fields) => {
1950 if ordered
1951 .iter()
1952 .any(|member| !fields.contains(&member.key.value.as_str()))
1953 {
1954 return Err(CodecErrorCode::InternalCoverageFailure);
1955 }
1956 ordered.sort_by_key(|member| {
1957 fields
1958 .iter()
1959 .position(|field| *field == member.key.value)
1960 .unwrap_or(fields.len())
1961 });
1962 }
1963 SemanticObjectOrder::DecodedKeys => ordered.sort_by(|left, right| {
1964 left.key.value.as_bytes().cmp(right.key.value.as_bytes())
1965 }),
1966 }
1967 for member in ordered {
1968 append_inventory_carrier(
1969 member.key_occurrence,
1970 inventory,
1971 selection,
1972 limit,
1973 output,
1974 )?;
1975 append_request_view(&member.value, inventory, selection, order, limit, output)?;
1976 }
1977 }
1978 },
1979 JsonKind::Array(values) => {
1980 for value in values {
1981 append_request_view(value, inventory, selection, order, limit, output)?;
1982 }
1983 }
1984 JsonKind::String(_) => {
1985 append_inventory_carrier(node.occurrence, inventory, selection, limit, output)?;
1986 }
1987 JsonKind::Number(_) | JsonKind::Bool(_) | JsonKind::Null => {}
1988 }
1989 Ok(())
1990}
1991
1992fn append_inventory_carrier(
1993 occurrence: usize,
1994 inventory: &FinalRequestCarrierInventory<'_>,
1995 selection: RequestViewSelection,
1996 limit: usize,
1997 output: &mut String,
1998) -> Result<(), CodecErrorCode> {
1999 let carrier = inventory.carrier(occurrence)?;
2000 let include = match selection {
2001 RequestViewSelection::Prompt(component) => {
2002 carrier.component == Some(component)
2003 && matches!(
2004 carrier.class,
2005 RequestCarrierClass::PromptMutable | RequestCarrierClass::PromptImmutable
2006 )
2007 }
2008 RequestViewSelection::Metadata => carrier.class == RequestCarrierClass::MetadataMutable,
2009 };
2010 if include {
2011 append_bounded(output, carrier.text, limit)?;
2012 }
2013 Ok(())
2014}
2015
2016fn append_bounded(output: &mut String, value: &str, limit: usize) -> Result<(), CodecErrorCode> {
2017 let new_len = output
2018 .len()
2019 .checked_add(value.len())
2020 .ok_or(CodecErrorCode::BodyLimitExceeded)?;
2021 if new_len > limit {
2022 return Err(CodecErrorCode::BodyLimitExceeded);
2023 }
2024 output
2025 .try_reserve(value.len())
2026 .map_err(|_| CodecErrorCode::BodyLimitExceeded)?;
2027 output.push_str(value);
2028 Ok(())
2029}
2030
2031#[derive(Default)]
2032struct RequestViewProbeOutcome {
2033 would_mutate: bool,
2034 contains_opaque_carrier: bool,
2035}
2036
2037impl RequestViewProbeOutcome {
2038 fn include(&mut self, other: Self) {
2039 self.would_mutate |= other.would_mutate;
2040 self.contains_opaque_carrier |= other.contains_opaque_carrier;
2041 }
2042}
2043
2044fn probe_request_view(
2045 view: &str,
2046 ctx: &mut RequestTransformContext<'_>,
2047) -> Result<RequestViewProbeOutcome, CodecErrorCode> {
2048 let contains_opaque_carrier = match guard_request_mutable_text(view, ctx) {
2049 Ok(()) => false,
2050 Err(CodecErrorCode::OpaqueMediaUninspected) => true,
2051 Err(code) => return Err(code),
2052 };
2053 ctx.validate_token_shapes(view)?;
2054 let transformed = Zeroizing::new(ctx.probe_without_prefix_cache_write(view)?);
2055 Ok(RequestViewProbeOutcome {
2056 would_mutate: transformed.as_bytes() != view.as_bytes(),
2057 contains_opaque_carrier,
2058 })
2059}
2060
2061fn prove_prompt_order(
2062 root: &JsonNode,
2063 inventory: &FinalRequestCarrierInventory<'_>,
2064 order: CarrierOrder,
2065 limit: usize,
2066 ctx: &mut RequestTransformContext<'_>,
2067) -> Result<RequestViewProbeOutcome, CodecErrorCode> {
2068 let mut components = Vec::new();
2069 for component in [
2070 PromptComponent::System,
2071 PromptComponent::Tools,
2072 PromptComponent::Messages,
2073 ] {
2074 if inventory.prompt_component_present(component) {
2075 components.push((
2076 component,
2077 build_prompt_component_view(root, inventory, component, order, limit)?,
2078 ));
2079 }
2080 }
2081 if components.is_empty() {
2082 return Ok(RequestViewProbeOutcome::default());
2083 }
2084 let permutations: &[&[usize]] = match components.len() {
2085 1 => &[&[0]],
2086 2 => &[&[0, 1], &[1, 0]],
2087 3 => &[
2088 &[0, 1, 2],
2089 &[0, 2, 1],
2090 &[1, 0, 2],
2091 &[1, 2, 0],
2092 &[2, 0, 1],
2093 &[2, 1, 0],
2094 ],
2095 _ => return Err(CodecErrorCode::InternalCoverageFailure),
2096 };
2097 let mut outcome = RequestViewProbeOutcome::default();
2098 for permutation in permutations {
2099 let mut view = Zeroizing::new(String::new());
2100 for index in *permutation {
2101 append_bounded(&mut view, &components[*index].1, limit)?;
2102 }
2103 outcome.include(probe_request_view(&view, ctx)?);
2104 }
2105 Ok(outcome)
2106}
2107
2108fn prove_request_logical_domains(
2109 root: &JsonNode,
2110 occurrence_count: usize,
2111 ctx: &mut RequestTransformContext<'_>,
2112) -> Result<(), CodecErrorCode> {
2113 let inventory = collect_final_request_carriers(root, occurrence_count)?;
2114 let limit = ctx
2115 .limits()
2116 .max_body_bytes()
2117 .min(MAX_ANTHROPIC_REQUEST_BODY_BYTES);
2118 let mut outcome = RequestViewProbeOutcome::default();
2119 for order in [CarrierOrder::Semantic, CarrierOrder::Emitted] {
2120 outcome.include(prove_prompt_order(root, &inventory, order, limit, ctx)?);
2121 }
2122 if inventory.metadata_present() {
2123 for order in [CarrierOrder::Semantic, CarrierOrder::Emitted] {
2124 let view = build_metadata_view(root, &inventory, order, limit)?;
2125 outcome.include(probe_request_view(&view, ctx)?);
2126 }
2127 }
2128 if outcome.contains_opaque_carrier {
2129 Err(CodecErrorCode::OpaqueMediaUninspected)
2130 } else if outcome.would_mutate {
2131 Err(CodecErrorCode::ControlWouldMutate)
2132 } else {
2133 Ok(())
2134 }
2135}
2136
2137fn account_subtree(node: &JsonNode, ledger: &mut CoverageLedger) -> Result<(), CodecErrorCode> {
2138 ledger.mark(node.occurrence)?;
2139 match &node.kind {
2140 JsonKind::Object(members) => {
2141 for member in members {
2142 ledger.mark(member.key_occurrence)?;
2143 account_subtree(&member.value, ledger)?;
2144 }
2145 }
2146 JsonKind::Array(values) => {
2147 for value in values {
2148 account_subtree(value, ledger)?;
2149 }
2150 }
2151 JsonKind::String(_) | JsonKind::Number(_) | JsonKind::Bool(_) | JsonKind::Null => {}
2152 }
2153 Ok(())
2154}
2155
2156fn transform_request_root(
2157 node: &mut JsonNode,
2158 input: &[u8],
2159 ctx: &mut RequestTransformContext<'_>,
2160 ledger: &mut CoverageLedger,
2161) -> Result<(), CodecErrorCode> {
2162 ledger.mark(node.occurrence)?;
2163 let JsonKind::Object(members) = &mut node.kind else {
2164 return Err(CodecErrorCode::UnsupportedRequestSurface);
2165 };
2166 let mut saw_model = false;
2167 let mut saw_max_tokens = false;
2168 let mut saw_messages = false;
2169 for member in members {
2170 ledger.mark(member.key_occurrence)?;
2171 match member.key.value.as_str() {
2172 "model" => {
2173 request_nonempty_string_control(&mut member.value, ctx, ledger)?;
2174 saw_model = true;
2175 }
2176 "service_tier" => request_enum_string_control(
2177 &mut member.value,
2178 &["auto", "standard_only"],
2179 ctx,
2180 ledger,
2181 )?,
2182 "max_tokens" => {
2183 request_integer_control(&member.value, ledger, true)?;
2184 saw_max_tokens = true;
2185 }
2186 "top_k" => request_integer_control(&member.value, ledger, false)?,
2187 "temperature" | "top_p" => request_unit_interval_control(&member.value, ledger)?,
2188 "stream" => request_bool_control(&member.value, ledger)?,
2189 "stop_sequences" => request_string_array_control(&mut member.value, ctx, ledger)?,
2190 "system" => transform_request_system(&mut member.value, input, ctx, ledger)?,
2191 "messages" => {
2192 transform_request_messages(&mut member.value, input, ctx, ledger)?;
2193 saw_messages = true;
2194 }
2195 "tools" => transform_request_tools(&mut member.value, input, ctx, ledger)?,
2196 "metadata" => transform_request_mutable(&mut member.value, ctx, ledger)?,
2197 "thinking" => transform_thinking_control(&mut member.value, ctx, ledger)?,
2198 "tool_choice" => transform_tool_choice_control(&mut member.value, ctx, ledger)?,
2199 "cache_control" => transform_cache_control(&mut member.value, ctx, ledger)?,
2200 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2201 }
2202 }
2203 if saw_model && saw_max_tokens && saw_messages {
2204 Ok(())
2205 } else {
2206 Err(CodecErrorCode::UnsupportedRequestSurface)
2207 }
2208}
2209
2210fn transform_request_messages(
2211 node: &mut JsonNode,
2212 input: &[u8],
2213 ctx: &mut RequestTransformContext<'_>,
2214 ledger: &mut CoverageLedger,
2215) -> Result<(), CodecErrorCode> {
2216 ledger.mark(node.occurrence)?;
2217 let JsonKind::Array(messages) = &mut node.kind else {
2218 return Err(CodecErrorCode::UnsupportedRequestSurface);
2219 };
2220 for message in messages {
2221 ledger.mark(message.occurrence)?;
2222 let JsonKind::Object(members) = &mut message.kind else {
2223 return Err(CodecErrorCode::UnsupportedRequestSurface);
2224 };
2225 let mut saw_role = false;
2226 let mut saw_content = false;
2227 for member in members {
2228 ledger.mark(member.key_occurrence)?;
2229 match member.key.value.as_str() {
2230 "role" => {
2231 request_enum_string_control(
2232 &mut member.value,
2233 &["user", "assistant"],
2234 ctx,
2235 ledger,
2236 )?;
2237 saw_role = true;
2238 }
2239 "content" => {
2240 transform_request_content(&mut member.value, input, ctx, ledger)?;
2241 saw_content = true;
2242 }
2243 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2244 }
2245 }
2246 if !saw_role || !saw_content {
2247 return Err(CodecErrorCode::UnsupportedRequestSurface);
2248 }
2249 }
2250 Ok(())
2251}
2252
2253fn transform_request_content(
2254 node: &mut JsonNode,
2255 input: &[u8],
2256 ctx: &mut RequestTransformContext<'_>,
2257 ledger: &mut CoverageLedger,
2258) -> Result<(), CodecErrorCode> {
2259 match &mut node.kind {
2260 JsonKind::String(_) => transform_request_string(node, ctx, ledger),
2261 JsonKind::Array(blocks) => {
2262 ledger.mark(node.occurrence)?;
2263 if blocks.is_empty() {
2264 return Err(CodecErrorCode::UnsupportedRequestSurface);
2265 }
2266 for (index, block) in blocks.iter_mut().enumerate() {
2267 transform_request_block_at_index(block, index, input, ctx, ledger)?;
2268 }
2269 Ok(())
2270 }
2271 _ => Err(CodecErrorCode::UnsupportedRequestSurface),
2272 }
2273}
2274
2275fn transform_request_system(
2276 node: &mut JsonNode,
2277 input: &[u8],
2278 ctx: &mut RequestTransformContext<'_>,
2279 ledger: &mut CoverageLedger,
2280) -> Result<(), CodecErrorCode> {
2281 match &mut node.kind {
2282 JsonKind::String(_) => transform_request_string(node, ctx, ledger),
2283 JsonKind::Array(blocks) => {
2284 ledger.mark(node.occurrence)?;
2285 if blocks.is_empty() {
2286 return Err(CodecErrorCode::UnsupportedRequestSurface);
2287 }
2288 for (index, block) in blocks.iter_mut().enumerate() {
2289 if object_string(block, "type")? != "text" {
2290 return Err(CodecErrorCode::UnsupportedRequestSurface);
2291 }
2292 transform_request_block_at_index(block, index, input, ctx, ledger)?;
2293 }
2294 Ok(())
2295 }
2296 _ => Err(CodecErrorCode::UnsupportedRequestSurface),
2297 }
2298}
2299
2300fn transform_request_block_at_index(
2301 node: &mut JsonNode,
2302 index: usize,
2303 input: &[u8],
2304 ctx: &mut RequestTransformContext<'_>,
2305 ledger: &mut CoverageLedger,
2306) -> Result<(), CodecErrorCode> {
2307 let previous = ctx.replace_content_block_index(Some(index));
2308 let result = transform_request_block(node, input, ctx, ledger);
2309 ctx.replace_content_block_index(previous);
2310 result
2311}
2312
2313fn transform_request_block(
2314 node: &mut JsonNode,
2315 input: &[u8],
2316 ctx: &mut RequestTransformContext<'_>,
2317 ledger: &mut CoverageLedger,
2318) -> Result<(), CodecErrorCode> {
2319 let block_type = object_string(node, "type")?.to_owned();
2320 if !matches!(
2321 block_type.as_str(),
2322 "text"
2323 | "tool_use"
2324 | "tool_result"
2325 | "document"
2326 | "search_result"
2327 | "custom_content"
2328 | "thinking"
2329 | "redacted_thinking"
2330 | "image"
2331 ) {
2332 return Err(CodecErrorCode::UnsupportedRequestSurface);
2333 }
2334 if block_type == "image" {
2335 return Err(CodecErrorCode::OpaqueMediaUninspected);
2336 }
2337 if matches!(block_type.as_str(), "thinking" | "redacted_thinking") {
2338 validate_signed_request_block(node, input, ctx, ledger, &block_type)?;
2339 node.raw_preserve = true;
2340 return Ok(());
2341 }
2342 ledger.mark(node.occurrence)?;
2343 let JsonKind::Object(members) = &mut node.kind else {
2344 return Err(CodecErrorCode::UnsupportedRequestSurface);
2345 };
2346 let mut saw_text = false;
2347 let mut saw_id = false;
2348 let mut saw_name = false;
2349 let mut saw_input = false;
2350 let mut saw_tool_use_id = false;
2351 let mut saw_content = false;
2352 let mut saw_source = false;
2353 let mut saw_user_payload = false;
2354 for member in members {
2355 ledger.mark(member.key_occurrence)?;
2356 match (block_type.as_str(), member.key.value.as_str()) {
2357 (_, "type") => {
2358 request_enum_string_control(
2359 &mut member.value,
2360 &[block_type.as_str()],
2361 ctx,
2362 ledger,
2363 )?;
2364 }
2365 (_, "cache_control") => transform_cache_control(&mut member.value, ctx, ledger)?,
2366 ("text", "text") => {
2367 transform_request_string(&mut member.value, ctx, ledger)?;
2368 saw_text = true;
2369 }
2370 ("tool_use", "id") => {
2371 request_nonempty_string_control(&mut member.value, ctx, ledger)?;
2372 saw_id = true;
2373 }
2374 ("tool_use", "name") => {
2375 request_nonempty_string_control(&mut member.value, ctx, ledger)?;
2376 saw_name = true;
2377 }
2378 ("tool_use", "input") => {
2379 transform_request_mutable(&mut member.value, ctx, ledger)?;
2380 saw_input = true;
2381 }
2382 ("tool_result", "tool_use_id") => {
2383 request_nonempty_string_control(&mut member.value, ctx, ledger)?;
2384 saw_tool_use_id = true;
2385 }
2386 ("tool_result", "content") => {
2387 transform_request_content(&mut member.value, input, ctx, ledger)?;
2388 saw_content = true;
2389 }
2390 ("tool_result", "is_error") => request_bool_control(&member.value, ledger)?,
2391 ("document", "source") => {
2392 transform_request_document_source(&mut member.value, ctx, ledger)?;
2393 saw_source = true;
2394 }
2395 ("document", "title" | "context") => {
2396 transform_request_mutable(&mut member.value, ctx, ledger)?
2397 }
2398 ("search_result" | "custom_content", "title" | "context" | "text" | "content") => {
2399 transform_request_mutable(&mut member.value, ctx, ledger)?;
2400 saw_user_payload = true;
2401 }
2402 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2403 }
2404 }
2405 let valid = match block_type.as_str() {
2406 "text" => saw_text,
2407 "tool_use" => saw_id && saw_name && saw_input,
2408 "tool_result" => saw_tool_use_id && saw_content,
2409 "document" => saw_source,
2410 "search_result" | "custom_content" => saw_user_payload,
2411 _ => false,
2412 };
2413 if valid {
2414 Ok(())
2415 } else {
2416 Err(CodecErrorCode::UnsupportedRequestSurface)
2417 }
2418}
2419
2420fn validate_signed_request_block(
2421 node: &JsonNode,
2422 input: &[u8],
2423 ctx: &mut RequestTransformContext<'_>,
2424 ledger: &mut CoverageLedger,
2425 block_type: &str,
2426) -> Result<(), CodecErrorCode> {
2427 account_subtree(node, ledger)?;
2428 let JsonKind::Object(members) = &node.kind else {
2429 return Err(CodecErrorCode::SignedSurfaceMalformed);
2430 };
2431 let mut has_payload = false;
2432 let mut has_signature = false;
2433 for member in members {
2434 match member.key.value.as_str() {
2435 "type" if string_value(&member.value)? == block_type => {}
2436 "cache_control" => validate_cache_control_probe(&member.value, ctx)?,
2437 "signature" => {
2438 ctx.validate_token_shapes(string_value(&member.value)?)?;
2439 has_signature = true;
2440 }
2441 "thinking" if block_type == "thinking" => {
2442 let text = string_value(&member.value)?;
2443 guard_request_mutable_text(text, ctx)?;
2444 ctx.validate_token_shapes(text)?;
2445 if ctx.probe_without_prefix_cache_write(text)? != text {
2446 return Err(CodecErrorCode::SignedMutationRequired);
2447 }
2448 has_payload = true;
2449 }
2450 "data" if block_type == "redacted_thinking" => {
2451 ctx.validate_token_shapes(string_value(&member.value)?)?;
2452 has_payload = true;
2453 }
2454 _ => return Err(CodecErrorCode::SignedSurfaceMalformed),
2455 }
2456 }
2457 if !has_payload || (block_type == "thinking" && !has_signature) || node.span.end > input.len() {
2458 return Err(CodecErrorCode::SignedSurfaceMalformed);
2459 }
2460 Ok(())
2461}
2462
2463fn probe_signed_request_value(
2464 node: &JsonNode,
2465 ctx: &mut RequestTransformContext<'_>,
2466) -> Result<(), CodecErrorCode> {
2467 match &node.kind {
2468 JsonKind::String(value) => {
2469 ctx.validate_token_shapes(&value.value)?;
2470 if ctx.probe_without_prefix_cache_write(&value.value)? != value.value {
2471 return Err(CodecErrorCode::SignedMutationRequired);
2472 }
2473 }
2474 JsonKind::Object(members) => {
2475 for member in members {
2476 ctx.validate_token_shapes(&member.key.value)?;
2477 if ctx.probe_without_prefix_cache_write(&member.key.value)? != member.key.value {
2478 return Err(CodecErrorCode::SignedMutationRequired);
2479 }
2480 probe_signed_request_value(&member.value, ctx)?;
2481 }
2482 }
2483 JsonKind::Array(values) => {
2484 for value in values {
2485 probe_signed_request_value(value, ctx)?;
2486 }
2487 }
2488 JsonKind::Number(_) | JsonKind::Bool(_) | JsonKind::Null => {}
2489 }
2490 Ok(())
2491}
2492
2493fn transform_request_document_source(
2494 node: &mut JsonNode,
2495 ctx: &mut RequestTransformContext<'_>,
2496 ledger: &mut CoverageLedger,
2497) -> Result<(), CodecErrorCode> {
2498 let source_type = object_string(node, "type")?.to_owned();
2499 if source_type != "text" && source_type != "content" {
2500 return Err(CodecErrorCode::OpaqueMediaUninspected);
2501 }
2502 ledger.mark(node.occurrence)?;
2503 let JsonKind::Object(members) = &mut node.kind else {
2504 return Err(CodecErrorCode::UnsupportedRequestSurface);
2505 };
2506 let mut saw_payload = false;
2507 for member in members {
2508 ledger.mark(member.key_occurrence)?;
2509 match member.key.value.as_str() {
2510 "type" => request_enum_string_control(
2511 &mut member.value,
2512 &[source_type.as_str()],
2513 ctx,
2514 ledger,
2515 )?,
2516 "media_type" => request_string_control(&mut member.value, ctx, ledger)?,
2517 "data" | "text" | "content" => {
2518 transform_request_mutable(&mut member.value, ctx, ledger)?;
2519 saw_payload = true;
2520 }
2521 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2522 }
2523 }
2524 if saw_payload {
2525 Ok(())
2526 } else {
2527 Err(CodecErrorCode::UnsupportedRequestSurface)
2528 }
2529}
2530
2531fn transform_request_tools(
2532 node: &mut JsonNode,
2533 _input: &[u8],
2534 ctx: &mut RequestTransformContext<'_>,
2535 ledger: &mut CoverageLedger,
2536) -> Result<(), CodecErrorCode> {
2537 ledger.mark(node.occurrence)?;
2538 let JsonKind::Array(tools) = &mut node.kind else {
2539 return Err(CodecErrorCode::UnsupportedRequestSurface);
2540 };
2541 if tools.is_empty() {
2542 return Err(CodecErrorCode::UnsupportedRequestSurface);
2543 }
2544 for tool in tools {
2545 ledger.mark(tool.occurrence)?;
2546 let JsonKind::Object(members) = &mut tool.kind else {
2547 return Err(CodecErrorCode::UnsupportedRequestSurface);
2548 };
2549 let mut saw_name = false;
2550 let mut saw_schema = false;
2551 for member in members {
2552 ledger.mark(member.key_occurrence)?;
2553 match member.key.value.as_str() {
2554 "name" => {
2555 request_nonempty_string_control(&mut member.value, ctx, ledger)?;
2556 saw_name = true;
2557 }
2558 "type" => request_enum_string_control(&mut member.value, &["custom"], ctx, ledger)?,
2559 "cache_control" => transform_cache_control(&mut member.value, ctx, ledger)?,
2560 "description" => transform_request_string(&mut member.value, ctx, ledger)?,
2561 "input_schema" => {
2562 if object_string(&member.value, "type")? != "object" {
2563 return Err(CodecErrorCode::UnsupportedRequestSurface);
2564 }
2565 transform_request_schema(&mut member.value, ctx, ledger)?;
2566 saw_schema = true;
2567 }
2568 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2569 }
2570 }
2571 if !saw_name || !saw_schema {
2572 return Err(CodecErrorCode::UnsupportedRequestSurface);
2573 }
2574 }
2575 Ok(())
2576}
2577
2578fn transform_request_schema(
2579 node: &mut JsonNode,
2580 ctx: &mut RequestTransformContext<'_>,
2581 ledger: &mut CoverageLedger,
2582) -> Result<(), CodecErrorCode> {
2583 match &mut node.kind {
2584 JsonKind::Object(members) => {
2585 ledger.mark(node.occurrence)?;
2586 let mut property_key_map = Vec::<(String, String)>::new();
2587 let mut transformed_property_keys = HashSet::new();
2588 if let Some(properties_member) = members
2589 .iter()
2590 .find(|member| member.key.value == "properties")
2591 {
2592 let JsonKind::Object(properties) = &properties_member.value.kind else {
2593 return Err(CodecErrorCode::UnsupportedRequestSurface);
2594 };
2595 for property in properties {
2596 guard_request_mutable_text(&property.key.value, ctx)?;
2597 ctx.validate_token_shapes(&property.key.value)?;
2598 let transformed = ctx.protect(&property.key.value)?;
2599 if !transformed_property_keys.insert(transformed.clone()) {
2600 return Err(CodecErrorCode::TransformedKeyCollision);
2601 }
2602 property_key_map.push((property.key.value.clone(), transformed));
2603 }
2604 }
2605 for member in members {
2606 ledger.mark(member.key_occurrence)?;
2607 match member.key.value.as_str() {
2608 "description" | "title" | "examples" | "default" => {
2609 transform_request_mutable(&mut member.value, ctx, ledger)?
2610 }
2611 "properties" => {
2612 ledger.mark(member.value.occurrence)?;
2613 let JsonKind::Object(properties) = &mut member.value.kind else {
2614 return Err(CodecErrorCode::UnsupportedRequestSurface);
2615 };
2616 for property in properties {
2617 ledger.mark(property.key_occurrence)?;
2618 property.key.value = property_key_map
2619 .iter()
2620 .find(|(original, _)| original == &property.key.value)
2621 .map(|(_, transformed)| transformed.clone())
2622 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
2623 transform_request_schema(&mut property.value, ctx, ledger)?;
2624 }
2625 }
2626 "$defs" => {
2627 ledger.mark(member.value.occurrence)?;
2628 let JsonKind::Object(definitions) = &mut member.value.kind else {
2629 return Err(CodecErrorCode::UnsupportedRequestSurface);
2630 };
2631 for definition in definitions {
2632 ledger.mark(definition.key_occurrence)?;
2633 guard_request_mutable_text(&definition.key.value, ctx)?;
2634 ctx.validate_token_shapes(&definition.key.value)?;
2635 if ctx.probe_without_prefix_cache_write(&definition.key.value)?
2636 != definition.key.value
2637 {
2638 return Err(CodecErrorCode::ControlWouldMutate);
2639 }
2640 transform_request_schema(&mut definition.value, ctx, ledger)?;
2641 }
2642 }
2643 "items" => transform_request_schema(&mut member.value, ctx, ledger)?,
2644 "additionalProperties" => {
2645 transform_additional_properties(&mut member.value, ctx, ledger)?
2646 }
2647 "anyOf" | "allOf" | "oneOf" => {
2648 transform_schema_list(&mut member.value, ctx, ledger)?
2649 }
2650 "required" => transform_required_property_names(
2651 &mut member.value,
2652 &property_key_map,
2653 ledger,
2654 )?,
2655 "type" => transform_schema_type(&mut member.value, ctx, ledger)?,
2656 "$ref" | "$id" | "$schema" | "format" | "pattern" => {
2657 request_string_control(&mut member.value, ctx, ledger)?
2658 }
2659 "const" => transform_schema_literal(&mut member.value, ctx, ledger)?,
2660 "enum" => transform_schema_enum(&mut member.value, ctx, ledger)?,
2661 "minimum" | "maximum" => {
2662 request_number_literal_control(&member.value, ctx, ledger)?
2663 }
2664 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2665 }
2666 }
2667 Ok(())
2668 }
2669 _ => Err(CodecErrorCode::UnsupportedRequestSurface),
2670 }
2671}
2672
2673fn transform_required_property_names(
2674 node: &mut JsonNode,
2675 property_key_map: &[(String, String)],
2676 ledger: &mut CoverageLedger,
2677) -> Result<(), CodecErrorCode> {
2678 ledger.mark(node.occurrence)?;
2679 let JsonKind::Array(values) = &mut node.kind else {
2680 return Err(CodecErrorCode::UnsupportedRequestSurface);
2681 };
2682 for value in values {
2683 ledger.mark(value.occurrence)?;
2684 let JsonKind::String(required) = &mut value.kind else {
2685 return Err(CodecErrorCode::UnsupportedRequestSurface);
2686 };
2687 required.value = property_key_map
2688 .iter()
2689 .find(|(original, _)| original == &required.value)
2690 .map(|(_, transformed)| transformed.clone())
2691 .ok_or(CodecErrorCode::UnsupportedRequestSurface)?;
2692 }
2693 Ok(())
2694}
2695
2696fn transform_request_mutable(
2697 node: &mut JsonNode,
2698 ctx: &mut RequestTransformContext<'_>,
2699 ledger: &mut CoverageLedger,
2700) -> Result<(), CodecErrorCode> {
2701 match &mut node.kind {
2702 JsonKind::Object(members) => {
2703 ledger.mark(node.occurrence)?;
2704 let mut transformed_keys = HashSet::new();
2705 for member in members {
2706 ledger.mark(member.key_occurrence)?;
2707 protect_key(&mut member.key, ctx)?;
2708 if !transformed_keys.insert(member.key.value.clone()) {
2709 return Err(CodecErrorCode::TransformedKeyCollision);
2710 }
2711 transform_request_mutable(&mut member.value, ctx, ledger)?;
2712 }
2713 Ok(())
2714 }
2715 JsonKind::Array(values) => {
2716 ledger.mark(node.occurrence)?;
2717 for value in values {
2718 transform_request_mutable(value, ctx, ledger)?;
2719 }
2720 Ok(())
2721 }
2722 JsonKind::String(_) => transform_request_string(node, ctx, ledger),
2723 JsonKind::Bool(_) | JsonKind::Null => ledger.mark(node.occurrence),
2724 JsonKind::Number(_) => {
2725 ledger.mark(node.occurrence)?;
2726 Err(CodecErrorCode::UnsupportedRequestSurface)
2727 }
2728 }
2729}
2730
2731fn transform_request_string(
2732 node: &mut JsonNode,
2733 ctx: &mut RequestTransformContext<'_>,
2734 ledger: &mut CoverageLedger,
2735) -> Result<(), CodecErrorCode> {
2736 ledger.mark(node.occurrence)?;
2737 let JsonKind::String(value) = &mut node.kind else {
2738 return Err(CodecErrorCode::UnsupportedRequestSurface);
2739 };
2740 guard_request_mutable_text(&value.value, ctx)?;
2741 ctx.validate_token_shapes(&value.value)?;
2742 value.value = ctx.protect(&value.value)?;
2743 Ok(())
2744}
2745
2746fn protect_key(
2747 key: &mut JsonString,
2748 ctx: &mut RequestTransformContext<'_>,
2749) -> Result<(), CodecErrorCode> {
2750 guard_request_mutable_text(&key.value, ctx)?;
2751 ctx.validate_token_shapes(&key.value)?;
2752 key.value = ctx.protect(&key.value)?;
2753 Ok(())
2754}
2755
2756fn request_string_control(
2757 node: &mut JsonNode,
2758 ctx: &mut RequestTransformContext<'_>,
2759 ledger: &mut CoverageLedger,
2760) -> Result<(), CodecErrorCode> {
2761 ledger.mark(node.occurrence)?;
2762 let JsonKind::String(value) = &node.kind else {
2763 return Err(CodecErrorCode::UnsupportedRequestSurface);
2764 };
2765 guard_request_mutable_text(&value.value, ctx)?;
2766 ctx.validate_token_shapes(&value.value)?;
2767 if ctx.probe_without_prefix_cache_write(&value.value)? == value.value {
2768 Ok(())
2769 } else {
2770 Err(CodecErrorCode::ControlWouldMutate)
2771 }
2772}
2773
2774fn request_nonempty_string_control(
2775 node: &mut JsonNode,
2776 ctx: &mut RequestTransformContext<'_>,
2777 ledger: &mut CoverageLedger,
2778) -> Result<(), CodecErrorCode> {
2779 if string_value(node)?.is_empty() {
2780 return Err(CodecErrorCode::UnsupportedRequestSurface);
2781 }
2782 request_string_control(node, ctx, ledger)
2783}
2784
2785fn request_enum_string_control(
2786 node: &mut JsonNode,
2787 allowed: &[&str],
2788 ctx: &mut RequestTransformContext<'_>,
2789 ledger: &mut CoverageLedger,
2790) -> Result<(), CodecErrorCode> {
2791 if !allowed.contains(&string_value(node)?) {
2792 return Err(CodecErrorCode::UnsupportedRequestSurface);
2793 }
2794 request_string_control(node, ctx, ledger)
2795}
2796
2797fn request_string_array_control(
2798 node: &mut JsonNode,
2799 ctx: &mut RequestTransformContext<'_>,
2800 ledger: &mut CoverageLedger,
2801) -> Result<(), CodecErrorCode> {
2802 ledger.mark(node.occurrence)?;
2803 let JsonKind::Array(values) = &mut node.kind else {
2804 return Err(CodecErrorCode::UnsupportedRequestSurface);
2805 };
2806 for value in values {
2807 request_string_control(value, ctx, ledger)?;
2808 }
2809 Ok(())
2810}
2811
2812fn transform_cache_control(
2813 node: &mut JsonNode,
2814 ctx: &mut RequestTransformContext<'_>,
2815 ledger: &mut CoverageLedger,
2816) -> Result<(), CodecErrorCode> {
2817 ledger.mark(node.occurrence)?;
2818 let JsonKind::Object(members) = &mut node.kind else {
2819 return Err(CodecErrorCode::UnsupportedRequestSurface);
2820 };
2821 let mut saw_type = false;
2822 for member in members {
2823 ledger.mark(member.key_occurrence)?;
2824 match member.key.value.as_str() {
2825 "type" => {
2826 request_enum_string_control(&mut member.value, &["ephemeral"], ctx, ledger)?;
2827 saw_type = true;
2828 }
2829 "ttl" => request_enum_string_control(&mut member.value, &["5m", "1h"], ctx, ledger)?,
2830 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2831 }
2832 }
2833 if !saw_type {
2834 return Err(CodecErrorCode::UnsupportedRequestSurface);
2835 }
2836 Ok(())
2837}
2838
2839fn validate_cache_control_probe(
2840 node: &JsonNode,
2841 ctx: &mut RequestTransformContext<'_>,
2842) -> Result<(), CodecErrorCode> {
2843 let JsonKind::Object(members) = &node.kind else {
2844 return Err(CodecErrorCode::SignedSurfaceMalformed);
2845 };
2846 let mut saw_type = false;
2847 for member in members {
2848 let value = string_value(&member.value)?;
2849 let valid = match member.key.value.as_str() {
2850 "type" => {
2851 saw_type = true;
2852 value == "ephemeral"
2853 }
2854 "ttl" => matches!(value, "5m" | "1h"),
2855 _ => false,
2856 };
2857 if !valid {
2858 return Err(CodecErrorCode::SignedSurfaceMalformed);
2859 }
2860 guard_request_mutable_text(value, ctx)?;
2861 ctx.validate_token_shapes(value)?;
2862 if ctx.probe_without_prefix_cache_write(value)? != value {
2863 return Err(CodecErrorCode::SignedMutationRequired);
2864 }
2865 }
2866 if saw_type {
2867 Ok(())
2868 } else {
2869 Err(CodecErrorCode::SignedSurfaceMalformed)
2870 }
2871}
2872
2873fn transform_thinking_control(
2874 node: &mut JsonNode,
2875 ctx: &mut RequestTransformContext<'_>,
2876 ledger: &mut CoverageLedger,
2877) -> Result<(), CodecErrorCode> {
2878 let thinking_type = object_string(node, "type")?.to_owned();
2879 if !matches!(thinking_type.as_str(), "enabled" | "disabled") {
2880 return Err(CodecErrorCode::UnsupportedRequestSurface);
2881 }
2882 ledger.mark(node.occurrence)?;
2883 let JsonKind::Object(members) = &mut node.kind else {
2884 return Err(CodecErrorCode::UnsupportedRequestSurface);
2885 };
2886 let mut saw_type = false;
2887 let mut saw_budget = false;
2888 for member in members {
2889 ledger.mark(member.key_occurrence)?;
2890 match member.key.value.as_str() {
2891 "type" => {
2892 request_enum_string_control(
2893 &mut member.value,
2894 &[thinking_type.as_str()],
2895 ctx,
2896 ledger,
2897 )?;
2898 saw_type = true;
2899 }
2900 "budget_tokens" if thinking_type == "enabled" => {
2901 request_integer_control(&member.value, ledger, true)?;
2902 saw_budget = true;
2903 }
2904 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2905 }
2906 }
2907 if saw_type && (thinking_type == "disabled" || saw_budget) {
2908 Ok(())
2909 } else {
2910 Err(CodecErrorCode::UnsupportedRequestSurface)
2911 }
2912}
2913
2914fn transform_tool_choice_control(
2915 node: &mut JsonNode,
2916 ctx: &mut RequestTransformContext<'_>,
2917 ledger: &mut CoverageLedger,
2918) -> Result<(), CodecErrorCode> {
2919 let choice_type = object_string(node, "type")?.to_owned();
2920 if !matches!(choice_type.as_str(), "auto" | "any" | "tool" | "none") {
2921 return Err(CodecErrorCode::UnsupportedRequestSurface);
2922 }
2923 ledger.mark(node.occurrence)?;
2924 let JsonKind::Object(members) = &mut node.kind else {
2925 return Err(CodecErrorCode::UnsupportedRequestSurface);
2926 };
2927 let mut saw_type = false;
2928 let mut saw_name = false;
2929 for member in members {
2930 ledger.mark(member.key_occurrence)?;
2931 match member.key.value.as_str() {
2932 "type" => {
2933 request_enum_string_control(
2934 &mut member.value,
2935 &[choice_type.as_str()],
2936 ctx,
2937 ledger,
2938 )?;
2939 saw_type = true;
2940 }
2941 "name" if choice_type == "tool" => {
2942 request_nonempty_string_control(&mut member.value, ctx, ledger)?;
2943 saw_name = true;
2944 }
2945 "disable_parallel_tool_use" => request_bool_control(&member.value, ledger)?,
2946 _ => return Err(CodecErrorCode::UnsupportedRequestSurface),
2947 }
2948 }
2949 if saw_type && (choice_type != "tool" || saw_name) {
2950 Ok(())
2951 } else {
2952 Err(CodecErrorCode::UnsupportedRequestSurface)
2953 }
2954}
2955
2956fn transform_schema_type(
2957 node: &mut JsonNode,
2958 ctx: &mut RequestTransformContext<'_>,
2959 ledger: &mut CoverageLedger,
2960) -> Result<(), CodecErrorCode> {
2961 const TYPES: &[&str] = &[
2962 "object", "array", "string", "number", "integer", "boolean", "null",
2963 ];
2964 match &mut node.kind {
2965 JsonKind::String(_) => request_enum_string_control(node, TYPES, ctx, ledger),
2966 JsonKind::Array(values) => {
2967 ledger.mark(node.occurrence)?;
2968 if values.is_empty() {
2969 return Err(CodecErrorCode::UnsupportedRequestSurface);
2970 }
2971 for value in values {
2972 request_enum_string_control(value, TYPES, ctx, ledger)?;
2973 }
2974 Ok(())
2975 }
2976 _ => Err(CodecErrorCode::UnsupportedRequestSurface),
2977 }
2978}
2979
2980fn transform_additional_properties(
2981 node: &mut JsonNode,
2982 ctx: &mut RequestTransformContext<'_>,
2983 ledger: &mut CoverageLedger,
2984) -> Result<(), CodecErrorCode> {
2985 if matches!(&node.kind, JsonKind::Bool(_)) {
2986 request_bool_control(node, ledger)
2987 } else if matches!(&node.kind, JsonKind::Object(_)) {
2988 transform_request_schema(node, ctx, ledger)
2989 } else {
2990 Err(CodecErrorCode::UnsupportedRequestSurface)
2991 }
2992}
2993
2994fn transform_schema_list(
2995 node: &mut JsonNode,
2996 ctx: &mut RequestTransformContext<'_>,
2997 ledger: &mut CoverageLedger,
2998) -> Result<(), CodecErrorCode> {
2999 ledger.mark(node.occurrence)?;
3000 let JsonKind::Array(values) = &mut node.kind else {
3001 return Err(CodecErrorCode::UnsupportedRequestSurface);
3002 };
3003 if values.is_empty() {
3004 return Err(CodecErrorCode::UnsupportedRequestSurface);
3005 }
3006 for value in values {
3007 transform_request_schema(value, ctx, ledger)?;
3008 }
3009 Ok(())
3010}
3011
3012fn transform_schema_literal(
3013 node: &mut JsonNode,
3014 ctx: &mut RequestTransformContext<'_>,
3015 ledger: &mut CoverageLedger,
3016) -> Result<(), CodecErrorCode> {
3017 match &node.kind {
3018 JsonKind::String(_) => request_string_control(node, ctx, ledger),
3019 JsonKind::Number(_) => request_number_literal_control(node, ctx, ledger),
3020 JsonKind::Bool(_) | JsonKind::Null => ledger.mark(node.occurrence),
3021 JsonKind::Object(_) | JsonKind::Array(_) => Err(CodecErrorCode::UnsupportedRequestSurface),
3022 }
3023}
3024
3025fn transform_schema_enum(
3026 node: &mut JsonNode,
3027 ctx: &mut RequestTransformContext<'_>,
3028 ledger: &mut CoverageLedger,
3029) -> Result<(), CodecErrorCode> {
3030 ledger.mark(node.occurrence)?;
3031 let JsonKind::Array(values) = &mut node.kind else {
3032 return Err(CodecErrorCode::UnsupportedRequestSurface);
3033 };
3034 if values.is_empty() {
3035 return Err(CodecErrorCode::UnsupportedRequestSurface);
3036 }
3037 for value in values {
3038 transform_schema_literal(value, ctx, ledger)?;
3039 }
3040 Ok(())
3041}
3042
3043fn request_number_control(
3044 node: &JsonNode,
3045 ledger: &mut CoverageLedger,
3046) -> Result<(), CodecErrorCode> {
3047 ledger.mark(node.occurrence)?;
3048 if matches!(node.kind, JsonKind::Number(_)) {
3049 Ok(())
3050 } else {
3051 Err(CodecErrorCode::UnsupportedRequestSurface)
3052 }
3053}
3054
3055fn request_number_literal_control(
3063 node: &JsonNode,
3064 ctx: &mut RequestTransformContext<'_>,
3065 ledger: &mut CoverageLedger,
3066) -> Result<(), CodecErrorCode> {
3067 request_number_control(node, ledger)?;
3068 let JsonKind::Number(raw) = &node.kind else {
3069 return Err(CodecErrorCode::UnsupportedRequestSurface);
3070 };
3071 if ctx.probe_without_prefix_cache_write(raw)? == *raw {
3072 Ok(())
3073 } else {
3074 Err(CodecErrorCode::ControlWouldMutate)
3075 }
3076}
3077
3078fn request_integer_control(
3079 node: &JsonNode,
3080 ledger: &mut CoverageLedger,
3081 nonzero: bool,
3082) -> Result<(), CodecErrorCode> {
3083 ledger.mark(node.occurrence)?;
3084 let JsonKind::Number(raw) = &node.kind else {
3085 return Err(CodecErrorCode::UnsupportedRequestSurface);
3086 };
3087 if raw.starts_with('-') || raw.contains(['.', 'e', 'E']) {
3088 return Err(CodecErrorCode::UnsupportedRequestSurface);
3089 }
3090 let value = raw
3091 .parse::<u64>()
3092 .map_err(|_| CodecErrorCode::UnsupportedRequestSurface)?;
3093 if nonzero && value == 0 {
3094 return Err(CodecErrorCode::UnsupportedRequestSurface);
3095 }
3096 Ok(())
3097}
3098
3099fn request_unit_interval_control(
3100 node: &JsonNode,
3101 ledger: &mut CoverageLedger,
3102) -> Result<(), CodecErrorCode> {
3103 ledger.mark(node.occurrence)?;
3104 let JsonKind::Number(raw) = &node.kind else {
3105 return Err(CodecErrorCode::UnsupportedRequestSurface);
3106 };
3107 if raw.starts_with('-') {
3108 return Err(CodecErrorCode::UnsupportedRequestSurface);
3109 }
3110 let value = raw
3111 .parse::<f64>()
3112 .map_err(|_| CodecErrorCode::UnsupportedRequestSurface)?;
3113 if value.is_finite() && (0.0..=1.0).contains(&value) {
3114 Ok(())
3115 } else {
3116 Err(CodecErrorCode::UnsupportedRequestSurface)
3117 }
3118}
3119
3120fn request_bool_control(
3121 node: &JsonNode,
3122 ledger: &mut CoverageLedger,
3123) -> Result<(), CodecErrorCode> {
3124 ledger.mark(node.occurrence)?;
3125 if matches!(node.kind, JsonKind::Bool(_)) {
3126 Ok(())
3127 } else {
3128 Err(CodecErrorCode::UnsupportedRequestSurface)
3129 }
3130}
3131
3132fn guard_mutable_text(value: &str) -> Result<(), CodecErrorCode> {
3133 let mut inspected = value;
3134 let mut wrapper_depth = 0usize;
3135 while let Some(inner) = inspected
3136 .strip_prefix('<')
3137 .and_then(|inner| inner.strip_suffix('>'))
3138 {
3139 wrapper_depth += 1;
3140 if wrapper_depth > MAX_ANGLE_WRAPPER_DEPTH {
3141 return Err(CodecErrorCode::OpaqueMediaUninspected);
3142 }
3143 inspected = inner;
3144 }
3145 let lower = inspected.to_ascii_lowercase();
3146 if inspected.chars().any(|character| {
3147 (character.is_control() && !matches!(character, '\n' | '\r' | '\t'))
3148 || matches!(
3149 character,
3150 '\u{061c}'
3151 | '\u{200e}'
3152 | '\u{200f}'
3153 | '\u{202a}'..='\u{202e}'
3154 | '\u{2066}'..='\u{2069}'
3155 )
3156 }) || find_carrier_scheme(&lower).is_some()
3157 || inspected.as_bytes().windows(3).any(|window| {
3158 window[0] == b'%' && hex_value(window[1]).is_some() && hex_value(window[2]).is_some()
3159 })
3160 || (inspected.len() >= 64
3161 && (inspected.bytes().all(|byte| byte.is_ascii_hexdigit())
3162 || inspected.bytes().all(|byte| {
3163 byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'/' | b'=' | b'-' | b'_')
3164 })))
3165 {
3166 return Err(CodecErrorCode::OpaqueMediaUninspected);
3167 }
3168 Ok(())
3169}
3170
3171fn guard_request_mutable_text(
3172 value: &str,
3173 ctx: &mut RequestTransformContext<'_>,
3174) -> Result<(), CodecErrorCode> {
3175 let result = guard_mutable_text(value);
3176 if result == Err(CodecErrorCode::OpaqueMediaUninspected) {
3177 let lower = value.to_ascii_lowercase();
3178 if let Some((scheme, byte_offset, byte_length)) = find_carrier_scheme(&lower) {
3179 ctx.record_opaque_carrier(scheme, byte_offset, byte_length);
3180 }
3181 }
3182 result
3183}
3184
3185fn find_carrier_scheme(value: &str) -> Option<(OpaqueCarrierScheme, usize, usize)> {
3186 value.char_indices().find_map(|(index, _)| {
3187 let candidate = &value[index..];
3188 let (scheme, matched) = [
3189 (OpaqueCarrierScheme::Data, "data:"),
3190 (OpaqueCarrierScheme::File, "file:"),
3191 (OpaqueCarrierScheme::Http, "http://"),
3192 (OpaqueCarrierScheme::Https, "https://"),
3193 ]
3194 .into_iter()
3195 .find(|(_, literal)| candidate.starts_with(literal))?;
3196 let boundary = index == 0
3197 || !value[..index].chars().next_back().is_some_and(|character| {
3198 character.is_ascii_alphanumeric() || matches!(character, '_' | '-')
3199 });
3200 boundary.then_some((scheme, index, matched.len()))
3201 })
3202}
3203
3204fn transform_response_root(
3205 node: &mut JsonNode,
3206 input: &[u8],
3207 ctx: &mut ResponseTransformContext<'_>,
3208 ledger: &mut CoverageLedger,
3209) -> Result<(), CodecErrorCode> {
3210 ledger.mark(node.occurrence)?;
3211 let JsonKind::Object(members) = &mut node.kind else {
3212 return Err(CodecErrorCode::UnsupportedResponseSurface);
3213 };
3214 let mut saw_id = false;
3215 let mut saw_type = false;
3216 let mut saw_role = false;
3217 let mut saw_model = false;
3218 let mut saw_content = false;
3219 let mut saw_stop_reason = false;
3220 let mut saw_stop_sequence = false;
3221 let mut saw_usage = false;
3222 for member in members {
3223 ledger.mark(member.key_occurrence)?;
3224 match member.key.value.as_str() {
3225 "id" => {
3226 response_nonempty_string_control(&mut member.value, ctx, ledger)?;
3227 saw_id = true;
3228 }
3229 "type" => {
3230 response_enum_string_control(&mut member.value, &["message"], ctx, ledger)?;
3231 saw_type = true;
3232 }
3233 "role" => {
3234 response_enum_string_control(&mut member.value, &["assistant"], ctx, ledger)?;
3235 saw_role = true;
3236 }
3237 "model" => {
3238 response_nonempty_string_control(&mut member.value, ctx, ledger)?;
3239 saw_model = true;
3240 }
3241 "stop_reason" => {
3242 response_enum_string_or_null(
3243 &mut member.value,
3244 &[
3245 "end_turn",
3246 "max_tokens",
3247 "stop_sequence",
3248 "tool_use",
3249 "pause_turn",
3250 "refusal",
3251 "model_context_window_exceeded",
3252 ],
3253 ctx,
3254 ledger,
3255 )?;
3256 saw_stop_reason = true;
3257 }
3258 "stop_sequence" => {
3259 response_string_or_null(&mut member.value, ctx, ledger)?;
3260 saw_stop_sequence = true;
3261 }
3262 "content" => {
3263 transform_response_content(&mut member.value, input, ctx, ledger)?;
3264 saw_content = true;
3265 }
3266 "usage" => {
3267 response_usage(&mut member.value, ledger, true)?;
3268 saw_usage = true;
3269 }
3270 _ => return Err(CodecErrorCode::UnsupportedResponseSurface),
3271 }
3272 }
3273 if saw_id
3274 && saw_type
3275 && saw_role
3276 && saw_model
3277 && saw_content
3278 && saw_stop_reason
3279 && saw_stop_sequence
3280 && saw_usage
3281 {
3282 Ok(())
3283 } else {
3284 Err(CodecErrorCode::UnsupportedResponseSurface)
3285 }
3286}
3287
3288fn transform_response_content(
3289 node: &mut JsonNode,
3290 input: &[u8],
3291 ctx: &mut ResponseTransformContext<'_>,
3292 ledger: &mut CoverageLedger,
3293) -> Result<(), CodecErrorCode> {
3294 ledger.mark(node.occurrence)?;
3295 let JsonKind::Array(blocks) = &mut node.kind else {
3296 return Err(CodecErrorCode::UnsupportedResponseSurface);
3297 };
3298 for block in blocks {
3299 transform_response_block(block, input, ctx, ledger)?;
3300 }
3301 Ok(())
3302}
3303
3304fn transform_response_block(
3305 node: &mut JsonNode,
3306 input: &[u8],
3307 ctx: &mut ResponseTransformContext<'_>,
3308 ledger: &mut CoverageLedger,
3309) -> Result<(), CodecErrorCode> {
3310 let block_type = object_string(node, "type")?.to_owned();
3311 if !matches!(
3312 block_type.as_str(),
3313 "text"
3314 | "tool_use"
3315 | "tool_result"
3316 | "search_result"
3317 | "document"
3318 | "thinking"
3319 | "redacted_thinking"
3320 ) {
3321 return Err(CodecErrorCode::UnsupportedResponseSurface);
3322 }
3323 if matches!(block_type.as_str(), "thinking" | "redacted_thinking") {
3324 validate_signed_response_block(node, input, ctx, ledger, &block_type)?;
3325 node.raw_preserve = true;
3326 return Ok(());
3327 }
3328 ledger.mark(node.occurrence)?;
3329 let JsonKind::Object(members) = &mut node.kind else {
3330 return Err(CodecErrorCode::UnsupportedResponseSurface);
3331 };
3332 let mut saw_type = false;
3333 let mut saw_text = false;
3334 let mut saw_id = false;
3335 let mut saw_name = false;
3336 let mut saw_input = false;
3337 let mut saw_tool_use_id = false;
3338 let mut saw_content = false;
3339 let mut saw_user_payload = false;
3340 for member in members {
3341 ledger.mark(member.key_occurrence)?;
3342 match (block_type.as_str(), member.key.value.as_str()) {
3343 (_, "type") => {
3344 response_enum_string_control(
3345 &mut member.value,
3346 &[block_type.as_str()],
3347 ctx,
3348 ledger,
3349 )?;
3350 saw_type = true;
3351 }
3352 ("tool_use", "id") => {
3353 response_nonempty_string_control(&mut member.value, ctx, ledger)?;
3354 saw_id = true;
3355 }
3356 ("tool_use", "name") => {
3357 response_nonempty_string_control(&mut member.value, ctx, ledger)?;
3358 saw_name = true;
3359 }
3360 ("text", "text") => {
3361 transform_response_string(&mut member.value, ctx, ledger)?;
3362 saw_text = true;
3363 }
3364 ("text", "citations") => transform_response_citations(&mut member.value, ctx, ledger)?,
3365 ("tool_use", "input") => {
3366 transform_response_mutable(&mut member.value, ctx, ledger)?;
3367 saw_input = true;
3368 }
3369 ("tool_result", "content") => {
3370 transform_response_mutable(&mut member.value, ctx, ledger)?;
3371 saw_content = true;
3372 }
3373 ("tool_result", "tool_use_id") => {
3374 response_nonempty_string_control(&mut member.value, ctx, ledger)?;
3375 saw_tool_use_id = true;
3376 }
3377 ("tool_result", "is_error") => response_bool_or_null(&member.value, ledger)?,
3378 ("search_result" | "document", "title" | "context" | "text" | "content") => {
3379 transform_response_mutable(&mut member.value, ctx, ledger)?;
3380 saw_user_payload = true;
3381 }
3382 ("search_result" | "document", "source" | "id") => {
3383 response_string_control(&mut member.value, ctx, ledger)?
3384 }
3385 _ => return Err(CodecErrorCode::UnsupportedResponseSurface),
3386 }
3387 }
3388 let valid = match block_type.as_str() {
3389 "text" => saw_text,
3390 "tool_use" => saw_id && saw_name && saw_input,
3391 "tool_result" => saw_tool_use_id && saw_content,
3392 "search_result" | "document" => saw_user_payload,
3393 _ => false,
3394 };
3395 if saw_type && valid {
3396 Ok(())
3397 } else {
3398 Err(CodecErrorCode::UnsupportedResponseSurface)
3399 }
3400}
3401
3402fn validate_signed_response_block(
3403 node: &JsonNode,
3404 input: &[u8],
3405 ctx: &mut ResponseTransformContext<'_>,
3406 ledger: &mut CoverageLedger,
3407 block_type: &str,
3408) -> Result<(), CodecErrorCode> {
3409 account_subtree(node, ledger)?;
3410 let JsonKind::Object(members) = &node.kind else {
3411 return Err(CodecErrorCode::SignedSurfaceMalformed);
3412 };
3413 let mut has_payload = false;
3414 let mut has_type = false;
3415 let mut has_signature = false;
3416 for member in members {
3417 match member.key.value.as_str() {
3418 "type" if string_value(&member.value)? == block_type => {
3419 has_type = true;
3420 }
3421 "signature" => {
3422 let _ = ctx.restore(string_value(&member.value)?)?;
3423 has_signature = true;
3424 }
3425 "thinking" if block_type == "thinking" => {
3426 let text = string_value(&member.value)?;
3427 guard_mutable_text_response(text)?;
3428 let _ = ctx.restore(text)?;
3429 ctx.validate(text, &[])?;
3430 has_payload = true;
3431 }
3432 "data" if block_type == "redacted_thinking" => {
3433 let data = string_value(&member.value)?;
3434 let _ = ctx.restore(data)?;
3435 has_payload = true;
3436 }
3437 _ => return Err(CodecErrorCode::SignedSurfaceMalformed),
3438 }
3439 }
3440 if !has_type
3441 || !has_payload
3442 || (block_type == "thinking" && !has_signature)
3443 || node.span.end > input.len()
3444 {
3445 return Err(CodecErrorCode::SignedSurfaceMalformed);
3446 }
3447 Ok(())
3448}
3449
3450fn transform_response_mutable(
3451 node: &mut JsonNode,
3452 ctx: &mut ResponseTransformContext<'_>,
3453 ledger: &mut CoverageLedger,
3454) -> Result<(), CodecErrorCode> {
3455 match &mut node.kind {
3456 JsonKind::Object(members) => {
3457 ledger.mark(node.occurrence)?;
3458 let mut transformed_keys = HashSet::new();
3459 for member in members {
3460 ledger.mark(member.key_occurrence)?;
3461 restore_json_string(&mut member.key, ctx)?;
3462 if !transformed_keys.insert(member.key.value.clone()) {
3463 return Err(CodecErrorCode::TransformedKeyCollision);
3464 }
3465 transform_response_mutable(&mut member.value, ctx, ledger)?;
3466 }
3467 Ok(())
3468 }
3469 JsonKind::Array(values) => {
3470 ledger.mark(node.occurrence)?;
3471 for value in values.iter_mut() {
3472 transform_response_mutable(value, ctx, ledger)?;
3473 }
3474 let mut logical = String::new();
3475 let mut authorized = Vec::<Range<usize>>::new();
3476 for value in values {
3477 if let JsonKind::String(value) = &value.kind {
3478 let base = logical.len();
3479 logical.push_str(&value.value);
3480 authorized.extend(
3481 value
3482 .authorized
3483 .iter()
3484 .map(|range| base + range.start..base + range.end),
3485 );
3486 }
3487 }
3488 guard_mutable_text_response(&logical)?;
3489 ctx.validate(&logical, &authorized)?;
3490 Ok(())
3491 }
3492 JsonKind::String(_) => transform_response_string(node, ctx, ledger),
3493 JsonKind::Bool(_) | JsonKind::Null => ledger.mark(node.occurrence),
3494 JsonKind::Number(_) => {
3495 ledger.mark(node.occurrence)?;
3496 Err(CodecErrorCode::ProviderOriginNumeric)
3497 }
3498 }
3499}
3500
3501fn transform_response_string(
3502 node: &mut JsonNode,
3503 ctx: &mut ResponseTransformContext<'_>,
3504 ledger: &mut CoverageLedger,
3505) -> Result<(), CodecErrorCode> {
3506 ledger.mark(node.occurrence)?;
3507 let JsonKind::String(value) = &mut node.kind else {
3508 return Err(CodecErrorCode::UnsupportedResponseSurface);
3509 };
3510 restore_json_string(value, ctx)
3511}
3512
3513fn restore_json_string(
3514 value: &mut JsonString,
3515 ctx: &mut ResponseTransformContext<'_>,
3516) -> Result<(), CodecErrorCode> {
3517 guard_mutable_text_response(&value.value)?;
3518 let restored = ctx.restore(&value.value)?;
3519 ctx.validate(&restored.text, &restored.authorized_output_ranges)?;
3520 value.value = restored.text;
3521 value.authorized = restored.authorized_output_ranges;
3522 Ok(())
3523}
3524
3525fn guard_mutable_text_response(value: &str) -> Result<(), CodecErrorCode> {
3526 guard_mutable_text(value).map_err(|_| CodecErrorCode::ProviderOriginPii)
3527}
3528
3529fn transform_response_citations(
3530 node: &mut JsonNode,
3531 ctx: &mut ResponseTransformContext<'_>,
3532 ledger: &mut CoverageLedger,
3533) -> Result<(), CodecErrorCode> {
3534 ledger.mark(node.occurrence)?;
3535 let JsonKind::Array(citations) = &mut node.kind else {
3536 return Err(CodecErrorCode::UnsupportedResponseSurface);
3537 };
3538 for citation in citations {
3539 transform_response_citation(citation, ctx, ledger)?;
3540 }
3541 Ok(())
3542}
3543
3544fn transform_response_citation(
3545 citation: &mut JsonNode,
3546 ctx: &mut ResponseTransformContext<'_>,
3547 ledger: &mut CoverageLedger,
3548) -> Result<(), CodecErrorCode> {
3549 ledger.mark(citation.occurrence)?;
3550 let JsonKind::Object(members) = &mut citation.kind else {
3551 return Err(CodecErrorCode::UnsupportedResponseSurface);
3552 };
3553 for member in members {
3554 ledger.mark(member.key_occurrence)?;
3555 match member.key.value.as_str() {
3556 "cited_text" | "document_title" | "title" => {
3557 transform_response_string(&mut member.value, ctx, ledger)?
3558 }
3559 "type" | "url" | "source" => response_string_control(&mut member.value, ctx, ledger)?,
3560 "document_index"
3561 | "start_char_index"
3562 | "end_char_index"
3563 | "start_page_number"
3564 | "end_page_number"
3565 | "start_block_index"
3566 | "end_block_index"
3567 | "search_result_index" => response_numeric_control(&member.value, ledger)?,
3568 _ => return Err(CodecErrorCode::UnsupportedResponseSurface),
3569 }
3570 }
3571 Ok(())
3572}
3573
3574fn response_string_control(
3575 node: &mut JsonNode,
3576 ctx: &mut ResponseTransformContext<'_>,
3577 ledger: &mut CoverageLedger,
3578) -> Result<(), CodecErrorCode> {
3579 ledger.mark(node.occurrence)?;
3580 let JsonKind::String(value) = &node.kind else {
3581 return Err(CodecErrorCode::UnsupportedResponseSurface);
3582 };
3583 guard_mutable_text_response(&value.value)?;
3584 ctx.validate(&value.value, &[])
3585}
3586
3587fn response_nonempty_string_control(
3588 node: &mut JsonNode,
3589 ctx: &mut ResponseTransformContext<'_>,
3590 ledger: &mut CoverageLedger,
3591) -> Result<(), CodecErrorCode> {
3592 if string_value(node)?.is_empty() {
3593 return Err(CodecErrorCode::UnsupportedResponseSurface);
3594 }
3595 response_string_control(node, ctx, ledger)
3596}
3597
3598fn response_enum_string_control(
3599 node: &mut JsonNode,
3600 allowed: &[&str],
3601 ctx: &mut ResponseTransformContext<'_>,
3602 ledger: &mut CoverageLedger,
3603) -> Result<(), CodecErrorCode> {
3604 if !allowed.contains(&string_value(node)?) {
3605 return Err(CodecErrorCode::UnsupportedResponseSurface);
3606 }
3607 response_string_control(node, ctx, ledger)
3608}
3609
3610fn response_string_or_null(
3611 node: &mut JsonNode,
3612 ctx: &mut ResponseTransformContext<'_>,
3613 ledger: &mut CoverageLedger,
3614) -> Result<(), CodecErrorCode> {
3615 match &node.kind {
3616 JsonKind::String(_) => response_string_control(node, ctx, ledger),
3617 JsonKind::Null => ledger.mark(node.occurrence),
3618 _ => Err(CodecErrorCode::UnsupportedResponseSurface),
3619 }
3620}
3621
3622fn response_enum_string_or_null(
3623 node: &mut JsonNode,
3624 allowed: &[&str],
3625 ctx: &mut ResponseTransformContext<'_>,
3626 ledger: &mut CoverageLedger,
3627) -> Result<(), CodecErrorCode> {
3628 match &node.kind {
3629 JsonKind::String(_) => response_enum_string_control(node, allowed, ctx, ledger),
3630 JsonKind::Null => ledger.mark(node.occurrence),
3631 _ => Err(CodecErrorCode::UnsupportedResponseSurface),
3632 }
3633}
3634
3635fn response_numeric_control(
3636 node: &JsonNode,
3637 ledger: &mut CoverageLedger,
3638) -> Result<(), CodecErrorCode> {
3639 ledger.mark(node.occurrence)?;
3640 let JsonKind::Number(raw) = &node.kind else {
3641 return Err(CodecErrorCode::UnsupportedResponseSurface);
3642 };
3643 if raw.starts_with('-') || raw.contains(['.', 'e', 'E']) {
3644 return Err(CodecErrorCode::UnsupportedResponseSurface);
3645 }
3646 raw.parse::<u64>()
3647 .map(|_| ())
3648 .map_err(|_| CodecErrorCode::UnsupportedResponseSurface)
3649}
3650
3651fn response_usage(
3652 node: &mut JsonNode,
3653 ledger: &mut CoverageLedger,
3654 require_input_tokens: bool,
3655) -> Result<(), CodecErrorCode> {
3656 ledger.mark(node.occurrence)?;
3657 let JsonKind::Object(members) = &mut node.kind else {
3658 return Err(CodecErrorCode::UnsupportedResponseSurface);
3659 };
3660 let mut saw_input_tokens = false;
3661 let mut saw_output_tokens = false;
3662 for member in members {
3663 ledger.mark(member.key_occurrence)?;
3664 match member.key.value.as_str() {
3665 "input_tokens" => {
3666 response_numeric_control(&member.value, ledger)?;
3667 saw_input_tokens = true;
3668 }
3669 "output_tokens" => {
3670 response_numeric_control(&member.value, ledger)?;
3671 saw_output_tokens = true;
3672 }
3673 "cache_creation_input_tokens" | "cache_read_input_tokens" => {
3674 response_numeric_control(&member.value, ledger)?
3675 }
3676 _ => return Err(CodecErrorCode::UnsupportedResponseSurface),
3677 }
3678 }
3679 if (!require_input_tokens || saw_input_tokens) && saw_output_tokens {
3680 Ok(())
3681 } else {
3682 Err(CodecErrorCode::UnsupportedResponseSurface)
3683 }
3684}
3685
3686fn response_bool_or_null(
3687 node: &JsonNode,
3688 ledger: &mut CoverageLedger,
3689) -> Result<(), CodecErrorCode> {
3690 ledger.mark(node.occurrence)?;
3691 if matches!(&node.kind, JsonKind::Bool(_) | JsonKind::Null) {
3692 Ok(())
3693 } else {
3694 Err(CodecErrorCode::UnsupportedResponseSurface)
3695 }
3696}
3697
3698fn object_string<'a>(node: &'a JsonNode, key: &str) -> Result<&'a str, CodecErrorCode> {
3699 let JsonKind::Object(members) = &node.kind else {
3700 return Err(CodecErrorCode::InvalidJson);
3701 };
3702 let member = members
3703 .iter()
3704 .find(|member| member.key.value == key)
3705 .ok_or(CodecErrorCode::InvalidJson)?;
3706 string_value(&member.value)
3707}
3708
3709fn string_value(node: &JsonNode) -> Result<&str, CodecErrorCode> {
3710 let JsonKind::String(value) = &node.kind else {
3711 return Err(CodecErrorCode::InvalidJson);
3712 };
3713 Ok(&value.value)
3714}
3715
3716fn write_document(
3717 document: &JsonDocument,
3718 input: &[u8],
3719) -> Result<(Vec<u8>, OutputProvenance), CodecErrorCode> {
3720 let mut output = Vec::new();
3721 let mut provenance = OutputProvenance::default();
3722 write_json_node(&document.root, input, &mut output, &mut provenance)?;
3723 provenance.set_occurrence_count(document.occurrence_count);
3724 Ok((output, provenance))
3725}
3726
3727fn write_json_node(
3728 node: &JsonNode,
3729 input: &[u8],
3730 output: &mut Vec<u8>,
3731 provenance: &mut OutputProvenance,
3732) -> Result<(), CodecErrorCode> {
3733 if node.raw_preserve {
3734 let raw = input
3735 .get(node.span.clone())
3736 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
3737 let start = output.len();
3738 output.extend_from_slice(raw);
3739 provenance.record_signed(start..output.len());
3740 return Ok(());
3741 }
3742 match &node.kind {
3743 JsonKind::Object(members) => {
3744 output.push(b'{');
3745 for (index, member) in members.iter().enumerate() {
3746 if index > 0 {
3747 output.push(b',');
3748 }
3749 write_json_string(&member.key, output, provenance);
3750 output.push(b':');
3751 write_json_node(&member.value, input, output, provenance)?;
3752 }
3753 output.push(b'}');
3754 }
3755 JsonKind::Array(values) => {
3756 output.push(b'[');
3757 for (index, value) in values.iter().enumerate() {
3758 if index > 0 {
3759 output.push(b',');
3760 }
3761 write_json_node(value, input, output, provenance)?;
3762 }
3763 output.push(b']');
3764 }
3765 JsonKind::String(value) => write_json_string(value, output, provenance),
3766 JsonKind::Number(raw) => output.extend_from_slice(raw.as_bytes()),
3767 JsonKind::Bool(true) => output.extend_from_slice(b"true"),
3768 JsonKind::Bool(false) => output.extend_from_slice(b"false"),
3769 JsonKind::Null => output.extend_from_slice(b"null"),
3770 }
3771 Ok(())
3772}
3773
3774fn write_json_string(value: &JsonString, output: &mut Vec<u8>, provenance: &mut OutputProvenance) {
3775 output.push(b'"');
3776 for (logical_start, character) in value.value.char_indices() {
3777 let logical_end = logical_start + character.len_utf8();
3778 let authorized = value
3779 .authorized
3780 .iter()
3781 .any(|range| range.start <= logical_start && range.end >= logical_end);
3782 let wire_start = output.len();
3783 match character {
3784 '"' => output.extend_from_slice(br#"\""#),
3785 '\\' => output.extend_from_slice(br#"\\"#),
3786 '\u{0008}' => output.extend_from_slice(br"\b"),
3787 '\u{000c}' => output.extend_from_slice(br"\f"),
3788 '\n' => output.extend_from_slice(br"\n"),
3789 '\r' => output.extend_from_slice(br"\r"),
3790 '\t' => output.extend_from_slice(br"\t"),
3791 character if character <= '\u{001f}' => {
3792 output.extend_from_slice(format!("\\u{:04x}", u32::from(character)).as_bytes());
3793 }
3794 character => {
3795 let mut bytes = [0u8; 4];
3796 output.extend_from_slice(character.encode_utf8(&mut bytes).as_bytes());
3797 }
3798 }
3799 if authorized {
3800 provenance.record_authorized(wire_start..output.len());
3801 }
3802 }
3803 output.push(b'"');
3804}
3805
3806fn json_semantically_equal(left: &JsonNode, right: &JsonNode) -> bool {
3807 match (&left.kind, &right.kind) {
3808 (JsonKind::Object(left_members), JsonKind::Object(right_members)) => {
3809 left_members.len() == right_members.len()
3810 && left_members.iter().zip(right_members).all(|(left, right)| {
3811 left.key.value == right.key.value
3812 && json_semantically_equal(&left.value, &right.value)
3813 })
3814 }
3815 (JsonKind::Array(left_values), JsonKind::Array(right_values)) => {
3816 left_values.len() == right_values.len()
3817 && left_values
3818 .iter()
3819 .zip(right_values)
3820 .all(|(left, right)| json_semantically_equal(left, right))
3821 }
3822 (JsonKind::String(left), JsonKind::String(right)) => left.value == right.value,
3823 (JsonKind::Number(left), JsonKind::Number(right)) => left == right,
3824 (JsonKind::Bool(left), JsonKind::Bool(right)) => left == right,
3825 (JsonKind::Null, JsonKind::Null) => true,
3826 _ => false,
3827 }
3828}
3829
3830fn collect_signed_slices(node: &JsonNode, input: &[u8]) -> Result<Vec<Vec<u8>>, CodecErrorCode> {
3831 let mut slices = Vec::new();
3832 collect_signed_slices_into(node, input, &mut slices)?;
3833 Ok(slices)
3834}
3835
3836fn collect_signed_slices_into(
3837 node: &JsonNode,
3838 input: &[u8],
3839 slices: &mut Vec<Vec<u8>>,
3840) -> Result<(), CodecErrorCode> {
3841 if node.raw_preserve {
3842 slices.push(
3843 input
3844 .get(node.span.clone())
3845 .ok_or(CodecErrorCode::InternalCoverageFailure)?
3846 .to_vec(),
3847 );
3848 return Ok(());
3849 }
3850 match &node.kind {
3851 JsonKind::Object(members) => {
3852 for member in members {
3853 collect_signed_slices_into(&member.value, input, slices)?;
3854 }
3855 }
3856 JsonKind::Array(values) => {
3857 for value in values {
3858 collect_signed_slices_into(value, input, slices)?;
3859 }
3860 }
3861 JsonKind::String(_) | JsonKind::Number(_) | JsonKind::Bool(_) | JsonKind::Null => {}
3862 }
3863 Ok(())
3864}
3865
3866fn collect_authorized_wire_chunks(node: &JsonNode) -> Result<Vec<Vec<u8>>, CodecErrorCode> {
3867 let mut chunks = Vec::new();
3868 collect_authorized_wire_chunks_into(node, &mut chunks)?;
3869 Ok(chunks)
3870}
3871
3872fn collect_authorized_wire_chunks_into(
3873 node: &JsonNode,
3874 chunks: &mut Vec<Vec<u8>>,
3875) -> Result<(), CodecErrorCode> {
3876 if node.raw_preserve {
3877 return Ok(());
3878 }
3879 match &node.kind {
3880 JsonKind::Object(members) => {
3881 for member in members {
3882 collect_authorized_string_chunks(&member.key, chunks)?;
3883 collect_authorized_wire_chunks_into(&member.value, chunks)?;
3884 }
3885 }
3886 JsonKind::Array(values) => {
3887 for value in values {
3888 collect_authorized_wire_chunks_into(value, chunks)?;
3889 }
3890 }
3891 JsonKind::String(value) => collect_authorized_string_chunks(value, chunks)?,
3892 JsonKind::Number(_) | JsonKind::Bool(_) | JsonKind::Null => {}
3893 }
3894 Ok(())
3895}
3896
3897fn collect_authorized_string_chunks(
3898 value: &JsonString,
3899 chunks: &mut Vec<Vec<u8>>,
3900) -> Result<(), CodecErrorCode> {
3901 for range in &value.authorized {
3902 let logical = value
3903 .value
3904 .get(range.clone())
3905 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
3906 let mut encoded = Vec::new();
3907 write_json_string(
3908 &JsonString {
3909 value: logical.to_owned(),
3910 authorized: Vec::new(),
3911 },
3912 &mut encoded,
3913 &mut OutputProvenance::default(),
3914 );
3915 let content = encoded
3916 .get(1..encoded.len().saturating_sub(1))
3917 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
3918 chunks.push(content.to_vec());
3919 }
3920 Ok(())
3921}
3922
3923fn verify_final_wire_proof(
3924 output: &[u8],
3925 provenance: &OutputProvenance,
3926 expected_authorized: &[Vec<u8>],
3927 expected_signed: &[Vec<u8>],
3928) -> Result<(), CodecErrorCode> {
3929 verify_range_chunks(
3930 output,
3931 provenance.authorized_output_ranges(),
3932 expected_authorized,
3933 )?;
3934 verify_range_chunks(output, provenance.signed_opaque_ranges(), expected_signed)?;
3935 for authorized in provenance.authorized_output_ranges() {
3936 if provenance
3937 .signed_opaque_ranges()
3938 .iter()
3939 .any(|signed| authorized.start < signed.end && signed.start < authorized.end)
3940 {
3941 return Err(CodecErrorCode::InternalCoverageFailure);
3942 }
3943 }
3944 Ok(())
3945}
3946
3947fn verify_range_chunks(
3948 output: &[u8],
3949 ranges: &[Range<usize>],
3950 expected: &[Vec<u8>],
3951) -> Result<(), CodecErrorCode> {
3952 if ranges.len() != expected.len() {
3953 return Err(CodecErrorCode::InternalCoverageFailure);
3954 }
3955 let mut previous_end = 0usize;
3956 for (range, expected) in ranges.iter().zip(expected) {
3957 if range.start < previous_end
3958 || range.start >= range.end
3959 || range.end > output.len()
3960 || output.get(range.clone()) != Some(expected.as_slice())
3961 {
3962 return Err(CodecErrorCode::InternalCoverageFailure);
3963 }
3964 previous_end = range.end;
3965 }
3966 Ok(())
3967}
3968
3969struct SseDecoder {
3970 limits: CodecLimits,
3971 bytes: Vec<u8>,
3972}
3973
3974struct SseFrame {
3975 event: Option<String>,
3976 data: Vec<u8>,
3977 raw: Vec<u8>,
3978 comment: bool,
3979 inspection_fact: Option<SseInspectionFrameFactV1>,
3980}
3981
3982struct SseInspectionFrameFactV1 {
3983 event_kind: SseEventKindV1,
3984 delta_kind: ProjectionAvailabilityV1<SseDeltaKindV1>,
3985 content_block: ProjectionAvailabilityV1<ContentBlockIndexV1>,
3986}
3987
3988impl SseDecoder {
3989 fn new(limits: CodecLimits) -> Self {
3990 Self {
3991 limits,
3992 bytes: Vec::new(),
3993 }
3994 }
3995
3996 fn push(&mut self, chunk: &[u8]) -> Result<(), CodecError> {
3997 let next =
3998 self.bytes.len().checked_add(chunk.len()).ok_or_else(|| {
3999 codec_error(CodecErrorCode::BodyLimitExceeded, CodecPhase::SseDecode)
4000 })?;
4001 if next > self.limits.max_body_bytes() {
4002 return Err(codec_error(
4003 CodecErrorCode::BodyLimitExceeded,
4004 CodecPhase::SseDecode,
4005 ));
4006 }
4007 self.bytes.extend_from_slice(chunk);
4008 Ok(())
4009 }
4010
4011 fn input_len(&self) -> usize {
4012 self.bytes.len()
4013 }
4014
4015 fn finish(self) -> Result<Vec<SseFrame>, CodecError> {
4016 if self.bytes.starts_with(&[0xef, 0xbb, 0xbf]) || self.bytes.contains(&0) {
4017 return Err(codec_error(
4018 CodecErrorCode::InvalidSseFrame,
4019 CodecPhase::SseDecode,
4020 ));
4021 }
4022 std::str::from_utf8(&self.bytes)
4023 .map_err(|_| codec_error(CodecErrorCode::InvalidUtf8, CodecPhase::SseDecode))?;
4024 let lines = split_sse_lines(&self.bytes, self.limits)?;
4025 let mut frames = Vec::new();
4026 let mut event = None;
4027 let mut data_lines = Vec::<Vec<u8>>::new();
4028 let mut raw = Vec::new();
4029 let mut comment_only = false;
4030 for line in lines {
4031 if line.content.is_empty() {
4032 if comment_only && event.is_none() && data_lines.is_empty() {
4033 raw.extend_from_slice(line.raw);
4034 if raw.len() > self.limits.max_sse_frame_bytes() {
4035 return Err(codec_error(
4036 CodecErrorCode::SseFrameLimitExceeded,
4037 CodecPhase::SseDecode,
4038 ));
4039 }
4040 frames.push(SseFrame {
4041 event: None,
4042 data: Vec::new(),
4043 raw: std::mem::take(&mut raw),
4044 comment: true,
4045 inspection_fact: None,
4046 });
4047 } else if event.is_some() || !data_lines.is_empty() {
4048 raw.extend_from_slice(line.raw);
4049 if raw.len() > self.limits.max_sse_frame_bytes() {
4050 return Err(codec_error(
4051 CodecErrorCode::SseFrameLimitExceeded,
4052 CodecPhase::SseDecode,
4053 ));
4054 }
4055 let data_len = data_lines.iter().map(Vec::len).sum::<usize>()
4056 + data_lines.len().saturating_sub(1);
4057 if data_len > self.limits.max_sse_frame_bytes() {
4058 return Err(codec_error(
4059 CodecErrorCode::SseFrameLimitExceeded,
4060 CodecPhase::SseDecode,
4061 ));
4062 }
4063 let mut data = Vec::with_capacity(data_len);
4064 for (index, value) in data_lines.drain(..).enumerate() {
4065 if index > 0 {
4066 data.push(b'\n');
4067 }
4068 data.extend_from_slice(&value);
4069 }
4070 frames.push(SseFrame {
4071 event: event.take(),
4072 data,
4073 raw: std::mem::take(&mut raw),
4074 comment: false,
4075 inspection_fact: None,
4076 });
4077 } else if !raw.is_empty() {
4078 return Err(codec_error(
4079 CodecErrorCode::InvalidSseFrame,
4080 CodecPhase::SseDecode,
4081 ));
4082 }
4083 comment_only = false;
4084 if frames.len() > self.limits.max_sse_events() {
4085 return Err(codec_error(
4086 CodecErrorCode::SseEventLimitExceeded,
4087 CodecPhase::SseDecode,
4088 ));
4089 }
4090 continue;
4091 }
4092 raw.extend_from_slice(line.raw);
4093 if raw.len() > self.limits.max_sse_frame_bytes() {
4094 return Err(codec_error(
4095 CodecErrorCode::SseFrameLimitExceeded,
4096 CodecPhase::SseDecode,
4097 ));
4098 }
4099 if line.content.starts_with(b":") {
4100 if event.is_some() || !data_lines.is_empty() || comment_only {
4101 return Err(codec_error(
4102 CodecErrorCode::InvalidSseFrame,
4103 CodecPhase::SseDecode,
4104 ));
4105 }
4106 comment_only = true;
4107 continue;
4108 }
4109 if comment_only {
4110 return Err(codec_error(
4111 CodecErrorCode::InvalidSseFrame,
4112 CodecPhase::SseDecode,
4113 ));
4114 }
4115 let colon = line
4116 .content
4117 .iter()
4118 .position(|byte| *byte == b':')
4119 .ok_or_else(|| {
4120 codec_error(CodecErrorCode::InvalidSseField, CodecPhase::SseDecode)
4121 })?;
4122 let field = &line.content[..colon];
4123 let mut value = &line.content[colon + 1..];
4124 if value.first() == Some(&b' ') {
4125 value = &value[1..];
4126 }
4127 match field {
4128 b"event" => {
4129 if event.is_some() || !data_lines.is_empty() {
4130 return Err(codec_error(
4131 CodecErrorCode::InvalidSseField,
4132 CodecPhase::SseDecode,
4133 ));
4134 }
4135 event = Some(
4136 std::str::from_utf8(value)
4137 .map_err(|_| {
4138 codec_error(CodecErrorCode::InvalidUtf8, CodecPhase::SseDecode)
4139 })?
4140 .to_owned(),
4141 );
4142 }
4143 b"data" => data_lines.push(value.to_vec()),
4144 _ => {
4145 return Err(codec_error(
4146 CodecErrorCode::InvalidSseField,
4147 CodecPhase::SseDecode,
4148 ));
4149 }
4150 }
4151 }
4152 if event.is_some() || !data_lines.is_empty() || comment_only || !raw.is_empty() {
4153 return Err(codec_error(
4154 CodecErrorCode::IncompleteSseStream,
4155 CodecPhase::SseDecode,
4156 ));
4157 }
4158 Ok(frames)
4159 }
4160}
4161
4162struct SseLine<'a> {
4163 content: &'a [u8],
4164 raw: &'a [u8],
4165}
4166
4167fn split_sse_lines(bytes: &[u8], limits: CodecLimits) -> Result<Vec<SseLine<'_>>, CodecError> {
4168 let mut lines = Vec::new();
4169 let mut start = 0usize;
4170 let mut position = 0usize;
4171 while position < bytes.len() {
4172 if !matches!(bytes[position], b'\r' | b'\n') {
4173 position += 1;
4174 if position - start > limits.max_sse_line_bytes() {
4175 return Err(codec_error(
4176 CodecErrorCode::SseLineLimitExceeded,
4177 CodecPhase::SseDecode,
4178 ));
4179 }
4180 continue;
4181 }
4182 let content_end = position;
4183 if bytes[position] == b'\r' && bytes.get(position + 1) == Some(&b'\n') {
4184 position += 2;
4185 } else {
4186 position += 1;
4187 }
4188 lines.push(SseLine {
4189 content: &bytes[start..content_end],
4190 raw: &bytes[start..position],
4191 });
4192 start = position;
4193 }
4194 if start != bytes.len() {
4195 return Err(codec_error(
4196 CodecErrorCode::IncompleteSseStream,
4197 CodecPhase::SseDecode,
4198 ));
4199 }
4200 Ok(lines)
4201}
4202
4203#[derive(Clone, Copy, Eq, PartialEq)]
4204enum MessageLifecycle {
4205 AwaitingMessageStart,
4206 InMessage,
4207 MessageStopped,
4208}
4209
4210#[derive(Clone, Copy, Eq, PartialEq)]
4211enum BlockKind {
4212 Text,
4213 InputJson,
4214 Thinking,
4215 RedactedThinking,
4216}
4217
4218struct BlockAccumulator {
4219 kind: BlockKind,
4220 start_slot: usize,
4221 start_frame: Vec<u8>,
4222 delta_slots: Vec<usize>,
4223 logical: String,
4224 signature: String,
4225 signed_frames: Vec<(usize, Vec<u8>)>,
4226 passthrough_frames: Vec<(usize, Vec<u8>, OutputProvenance)>,
4227}
4228
4229#[derive(Default)]
4230struct ReplaySlot {
4231 bytes: Option<Vec<u8>>,
4232 provenance: OutputProvenance,
4233}
4234
4235fn restore_sse_frames(
4236 mut frames: Vec<SseFrame>,
4237 ctx: &mut ResponseTransformContext<'_>,
4238) -> Result<ProvedResponseBody, CodecError> {
4239 let mut lifecycle = MessageLifecycle::AwaitingMessageStart;
4240 let mut blocks = BTreeMap::<u32, BlockAccumulator>::new();
4241 let mut seen_indices = HashSet::<u32>::new();
4242 let mut slots = Vec::<ReplaySlot>::new();
4243 let mut concatenated_text = String::new();
4244 let mut concatenated_authorized = Vec::<Range<usize>>::new();
4245 let mut occurrence_count = 0usize;
4246 let mut inspection_facts_complete = true;
4247 for frame in &mut frames {
4248 if frame.comment {
4249 if matches!(lifecycle, MessageLifecycle::MessageStopped) {
4250 return Err(codec_error(
4251 CodecErrorCode::InvalidSseLifecycle,
4252 CodecPhase::SseLifecycle,
4253 ));
4254 }
4255 continue;
4256 }
4257 let event = frame
4258 .event
4259 .as_deref()
4260 .ok_or_else(|| codec_error(CodecErrorCode::InvalidSseFrame, CodecPhase::SseDecode))?;
4261 if matches!(lifecycle, MessageLifecycle::MessageStopped) {
4262 return Err(codec_error(
4263 CodecErrorCode::InvalidSseLifecycle,
4264 CodecPhase::SseLifecycle,
4265 ));
4266 }
4267 let mut document = parse_json(&frame.data, ctx.limits(), CodecPhase::SseDecode)?;
4268 occurrence_count = occurrence_count
4269 .checked_add(document.occurrence_count)
4270 .ok_or_else(|| {
4271 codec_error(CodecErrorCode::NodeLimitExceeded, CodecPhase::SseLifecycle)
4272 })?;
4273 let discriminator = object_string(&document.root, "type")
4274 .map_err(|code| codec_error(code, CodecPhase::SseDecode))?;
4275 if discriminator != event {
4276 return Err(codec_error(
4277 CodecErrorCode::InvalidSseEventPair,
4278 CodecPhase::SseDecode,
4279 ));
4280 }
4281 match event {
4282 "ping" => {
4283 validate_simple_event(&document, "ping")?;
4284 }
4285 "error" => {
4286 return Err(codec_error(
4287 CodecErrorCode::ProviderErrorEvent,
4288 CodecPhase::SseLifecycle,
4289 ));
4290 }
4291 "message_start" => {
4292 if !matches!(lifecycle, MessageLifecycle::AwaitingMessageStart) {
4293 return Err(codec_error(
4294 CodecErrorCode::InvalidSseLifecycle,
4295 CodecPhase::SseLifecycle,
4296 ));
4297 }
4298 transform_message_start(&mut document, &frame.data, ctx)?;
4299 let (bytes, provenance) = canonical_sse_frame(event, &document, &frame.data)?;
4300 slots.push(ReplaySlot {
4301 bytes: Some(bytes),
4302 provenance,
4303 });
4304 lifecycle = MessageLifecycle::InMessage;
4305 }
4306 "content_block_start" => {
4307 if !matches!(lifecycle, MessageLifecycle::InMessage) {
4308 return Err(codec_error(
4309 CodecErrorCode::InvalidSseLifecycle,
4310 CodecPhase::SseLifecycle,
4311 ));
4312 }
4313 if seen_indices.len() >= ctx.limits().max_sse_indices() {
4314 return Err(codec_error(
4315 CodecErrorCode::SseIndexLimitExceeded,
4316 CodecPhase::SseLifecycle,
4317 ));
4318 }
4319 let index = event_index(&document)?;
4320 if !seen_indices.insert(index) {
4321 return Err(codec_error(
4322 CodecErrorCode::InvalidContentBlockIndex,
4323 CodecPhase::SseLifecycle,
4324 ));
4325 }
4326 let slot = slots.len();
4327 slots.push(ReplaySlot::default());
4328 let accumulator =
4329 transform_block_start(&mut document, &frame.data, &frame.raw, slot, ctx)?;
4330 blocks.insert(index, accumulator);
4331 }
4332 "content_block_delta" => {
4333 if !matches!(lifecycle, MessageLifecycle::InMessage) {
4334 return Err(codec_error(
4335 CodecErrorCode::InvalidSseLifecycle,
4336 CodecPhase::SseLifecycle,
4337 ));
4338 }
4339 let index = event_index(&document)?;
4340 let accumulator = blocks.get_mut(&index).ok_or_else(|| {
4341 codec_error(
4342 CodecErrorCode::InvalidContentBlockIndex,
4343 CodecPhase::SseLifecycle,
4344 )
4345 })?;
4346 let slot = slots.len();
4347 slots.push(ReplaySlot::default());
4348 transform_block_delta(
4349 accumulator,
4350 &mut document,
4351 &frame.data,
4352 &frame.raw,
4353 slot,
4354 ctx,
4355 )?;
4356 }
4357 "content_block_stop" => {
4358 if !matches!(lifecycle, MessageLifecycle::InMessage) {
4359 return Err(codec_error(
4360 CodecErrorCode::InvalidSseLifecycle,
4361 CodecPhase::SseLifecycle,
4362 ));
4363 }
4364 let index = event_index(&document)?;
4365 let accumulator = blocks.remove(&index).ok_or_else(|| {
4366 codec_error(
4367 CodecErrorCode::InvalidContentBlockIndex,
4368 CodecPhase::SseLifecycle,
4369 )
4370 })?;
4371 let stop_slot = slots.len();
4372 slots.push(ReplaySlot::default());
4373 finish_block(
4374 index,
4375 accumulator,
4376 &mut slots,
4377 StopFrame {
4378 slot: stop_slot,
4379 document: &mut document,
4380 input: &frame.data,
4381 },
4382 ctx,
4383 &mut concatenated_text,
4384 &mut concatenated_authorized,
4385 )?;
4386 }
4387 "message_delta" => {
4388 if !matches!(lifecycle, MessageLifecycle::InMessage) || !blocks.is_empty() {
4389 return Err(codec_error(
4390 CodecErrorCode::InvalidSseLifecycle,
4391 CodecPhase::SseLifecycle,
4392 ));
4393 }
4394 transform_message_delta(&mut document, ctx)?;
4395 let (bytes, provenance) = canonical_sse_frame(event, &document, &frame.data)?;
4396 slots.push(ReplaySlot {
4397 bytes: Some(bytes),
4398 provenance,
4399 });
4400 }
4401 "message_stop" => {
4402 if !matches!(lifecycle, MessageLifecycle::InMessage) || !blocks.is_empty() {
4403 return Err(codec_error(
4404 CodecErrorCode::InvalidSseLifecycle,
4405 CodecPhase::SseLifecycle,
4406 ));
4407 }
4408 validate_simple_event(&document, "message_stop")?;
4409 let (bytes, provenance) = canonical_sse_frame(event, &document, &frame.data)?;
4410 slots.push(ReplaySlot {
4411 bytes: Some(bytes),
4412 provenance,
4413 });
4414 lifecycle = MessageLifecycle::MessageStopped;
4415 }
4416 _ => {
4417 validate_future_event(&document, event, ctx)?;
4418 }
4419 }
4420 if ctx.inspection_projection_requested() {
4421 frame.inspection_fact = sse_inspection_fact(&document, event);
4422 inspection_facts_complete &= frame.inspection_fact.is_some();
4423 }
4424 }
4425 if !matches!(lifecycle, MessageLifecycle::MessageStopped) || !blocks.is_empty() {
4426 return Err(codec_error(
4427 CodecErrorCode::IncompleteSseStream,
4428 CodecPhase::SseLifecycle,
4429 ));
4430 }
4431 ctx.validate(&concatenated_text, &concatenated_authorized)
4432 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
4433 let mut output = Vec::new();
4434 let mut provenance = OutputProvenance::default();
4435 let mut expected_authorized = Vec::<Vec<u8>>::new();
4436 let mut expected_signed = Vec::<Vec<u8>>::new();
4437 let mut expected_authorized_ranges = Vec::<Range<usize>>::new();
4438 let mut expected_signed_ranges = Vec::<Range<usize>>::new();
4439 for slot in slots {
4440 if let Some(bytes) = slot.bytes {
4441 let offset = output.len();
4442 for range in slot.provenance.authorized_output_ranges() {
4443 append_expected_wire_chunk(
4444 &bytes,
4445 range,
4446 offset,
4447 &mut expected_authorized_ranges,
4448 &mut expected_authorized,
4449 )
4450 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4451 }
4452 for range in slot.provenance.signed_opaque_ranges() {
4453 append_expected_wire_chunk(
4454 &bytes,
4455 range,
4456 offset,
4457 &mut expected_signed_ranges,
4458 &mut expected_signed,
4459 )
4460 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4461 }
4462 output.extend_from_slice(&bytes);
4463 for range in slot.provenance.authorized_output_ranges() {
4464 provenance.record_authorized(offset + range.start..offset + range.end);
4465 }
4466 for range in slot.provenance.signed_opaque_ranges() {
4467 provenance.record_signed(offset + range.start..offset + range.end);
4468 }
4469 }
4470 }
4471 if output.len() > ctx.limits().max_body_bytes() {
4472 return Err(codec_error(
4473 CodecErrorCode::BodyLimitExceeded,
4474 CodecPhase::SseReplay,
4475 ));
4476 }
4477 let final_occurrence_count = reprove_sse_output(&output, ctx.limits())?;
4478 provenance.set_occurrence_count(final_occurrence_count);
4479 verify_final_wire_proof(&output, &provenance, &expected_authorized, &expected_signed)
4480 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4481 provenance.mark_final_buffer_verified();
4482 if ctx.inspection_projection_requested() && provenance.signed_opaque_ranges().is_empty() {
4483 for frame in &mut frames {
4484 frame.event = None;
4485 frame.data = Vec::new();
4486 frame.raw = Vec::new();
4487 }
4488 Ok(ProvedResponseBody::new_with_inspection_parsed_facts(
4489 output,
4490 WireFormat::Sse,
4491 provenance,
4492 InspectionParsedFactsV1::AnthropicSse(SseInspectionParsedFactsV1 {
4493 frames,
4494 complete: inspection_facts_complete,
4495 }),
4496 ))
4497 } else {
4498 Ok(ProvedResponseBody::new(output, WireFormat::Sse, provenance))
4499 }
4500}
4501
4502fn sse_inspection_fact(document: &JsonDocument, event: &str) -> Option<SseInspectionFrameFactV1> {
4503 let event_kind = match event {
4504 "message_start" => SseEventKindV1::MessageStart,
4505 "content_block_start" => SseEventKindV1::ContentBlockStart,
4506 "content_block_delta" => SseEventKindV1::ContentBlockDelta,
4507 "content_block_stop" => SseEventKindV1::ContentBlockStop,
4508 "message_delta" => SseEventKindV1::MessageDelta,
4509 "message_stop" => SseEventKindV1::MessageStop,
4510 "ping" => SseEventKindV1::Ping,
4511 "error" => SseEventKindV1::Error,
4512 _ => SseEventKindV1::UnknownFuture,
4513 };
4514 let content_block = match event {
4515 "content_block_start" | "content_block_delta" | "content_block_stop" => {
4516 ProjectionAvailabilityV1::Present(ContentBlockIndexV1::new(event_index(document).ok()?))
4517 }
4518 _ => ProjectionAvailabilityV1::Omitted(ProjectionOmissionReasonV1::NotApplicable),
4519 };
4520 let delta_kind = if event == "content_block_delta" {
4521 let delta = object_value(&document.root, "delta").ok()?;
4522 let delta_type = object_string(delta, "type").ok()?;
4523 let kind = match delta_type {
4524 "text_delta" => SseDeltaKindV1::Text,
4525 "input_json_delta" => SseDeltaKindV1::InputJson,
4526 "thinking_delta" => SseDeltaKindV1::Thinking,
4527 "signature_delta" => SseDeltaKindV1::Signature,
4528 "citations_delta" => SseDeltaKindV1::Citation,
4529 _ => SseDeltaKindV1::UnknownFuture,
4530 };
4531 ProjectionAvailabilityV1::Present(kind)
4532 } else {
4533 ProjectionAvailabilityV1::Omitted(ProjectionOmissionReasonV1::NotApplicable)
4534 };
4535 Some(SseInspectionFrameFactV1 {
4536 event_kind,
4537 delta_kind,
4538 content_block,
4539 })
4540}
4541
4542fn append_expected_wire_chunk(
4543 source: &[u8],
4544 local: &Range<usize>,
4545 offset: usize,
4546 ranges: &mut Vec<Range<usize>>,
4547 chunks: &mut Vec<Vec<u8>>,
4548) -> Result<(), CodecErrorCode> {
4549 let chunk = source
4550 .get(local.clone())
4551 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
4552 let global = offset
4553 .checked_add(local.start)
4554 .ok_or(CodecErrorCode::InternalCoverageFailure)?
4555 ..offset
4556 .checked_add(local.end)
4557 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
4558 if let Some(previous) = ranges.last_mut() {
4559 if global.start <= previous.end {
4560 let overlap = previous.end.saturating_sub(global.start);
4561 if overlap > chunk.len() {
4562 return Err(CodecErrorCode::InternalCoverageFailure);
4563 }
4564 let previous_chunk = chunks
4565 .last_mut()
4566 .ok_or(CodecErrorCode::InternalCoverageFailure)?;
4567 if overlap > previous_chunk.len()
4568 || previous_chunk[previous_chunk.len() - overlap..] != chunk[..overlap]
4569 {
4570 return Err(CodecErrorCode::InternalCoverageFailure);
4571 }
4572 previous_chunk.extend_from_slice(&chunk[overlap..]);
4573 previous.end = previous.end.max(global.end);
4574 return Ok(());
4575 }
4576 }
4577 ranges.push(global);
4578 chunks.push(chunk.to_vec());
4579 Ok(())
4580}
4581
4582fn reprove_sse_output(output: &[u8], limits: CodecLimits) -> Result<usize, CodecError> {
4583 let mut decoder = SseDecoder::new(limits);
4584 decoder.push(output)?;
4585 let frames = decoder.finish()?;
4586 let mut reconstructed = Vec::with_capacity(output.len());
4587 let mut lifecycle = MessageLifecycle::AwaitingMessageStart;
4588 let mut blocks = BTreeMap::<u32, BlockKind>::new();
4589 let mut seen_indices = HashSet::<u32>::new();
4590 let mut occurrence_count = 0usize;
4591 for frame in frames {
4592 if frame.comment {
4593 return Err(codec_error(
4594 CodecErrorCode::InternalCoverageFailure,
4595 CodecPhase::SseReplay,
4596 ));
4597 }
4598 reconstructed.extend_from_slice(&frame.raw);
4599 let event = frame.event.as_deref().ok_or_else(|| {
4600 codec_error(
4601 CodecErrorCode::InternalCoverageFailure,
4602 CodecPhase::SseReplay,
4603 )
4604 })?;
4605 let document = parse_json(&frame.data, limits, CodecPhase::SseReplay)?;
4606 occurrence_count = occurrence_count
4607 .checked_add(document.occurrence_count)
4608 .ok_or_else(|| codec_error(CodecErrorCode::NodeLimitExceeded, CodecPhase::SseReplay))?;
4609 validate_full_coverage(&document).map_err(|_| {
4610 codec_error(
4611 CodecErrorCode::InternalCoverageFailure,
4612 CodecPhase::SseReplay,
4613 )
4614 })?;
4615 if object_string(&document.root, "type") != Ok(event) {
4616 return Err(codec_error(
4617 CodecErrorCode::InternalCoverageFailure,
4618 CodecPhase::SseReplay,
4619 ));
4620 }
4621 match event {
4622 "message_start" => {
4623 if lifecycle != MessageLifecycle::AwaitingMessageStart {
4624 return Err(codec_error(
4625 CodecErrorCode::InternalCoverageFailure,
4626 CodecPhase::SseReplay,
4627 ));
4628 }
4629 exact_object_shape(&document.root, &["type", "message"], &["type", "message"])
4630 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4631 let message = object_value(&document.root, "message")
4632 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4633 prove_response_root_shape(message)
4634 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4635 lifecycle = MessageLifecycle::InMessage;
4636 }
4637 "content_block_start" => {
4638 if lifecycle != MessageLifecycle::InMessage {
4639 return Err(codec_error(
4640 CodecErrorCode::InternalCoverageFailure,
4641 CodecPhase::SseReplay,
4642 ));
4643 }
4644 exact_object_shape(
4645 &document.root,
4646 &["type", "index", "content_block"],
4647 &["type", "index", "content_block"],
4648 )
4649 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4650 let index = event_index(&document)?;
4651 if seen_indices.len() >= limits.max_sse_indices()
4652 || !seen_indices.insert(index)
4653 || blocks.contains_key(&index)
4654 {
4655 return Err(codec_error(
4656 CodecErrorCode::InternalCoverageFailure,
4657 CodecPhase::SseReplay,
4658 ));
4659 }
4660 let content_block = object_value(&document.root, "content_block")
4661 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4662 let kind = prove_sse_block_start_shape(content_block)
4663 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4664 blocks.insert(index, kind);
4665 }
4666 "content_block_delta" => {
4667 if lifecycle != MessageLifecycle::InMessage {
4668 return Err(codec_error(
4669 CodecErrorCode::InternalCoverageFailure,
4670 CodecPhase::SseReplay,
4671 ));
4672 }
4673 exact_object_shape(
4674 &document.root,
4675 &["type", "index", "delta"],
4676 &["type", "index", "delta"],
4677 )
4678 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4679 let index = event_index(&document)?;
4680 let kind = blocks.get(&index).copied().ok_or_else(|| {
4681 codec_error(
4682 CodecErrorCode::InternalCoverageFailure,
4683 CodecPhase::SseReplay,
4684 )
4685 })?;
4686 let delta = object_value(&document.root, "delta")
4687 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4688 prove_sse_delta_shape(delta, kind, limits)
4689 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4690 }
4691 "content_block_stop" => {
4692 if lifecycle != MessageLifecycle::InMessage {
4693 return Err(codec_error(
4694 CodecErrorCode::InternalCoverageFailure,
4695 CodecPhase::SseReplay,
4696 ));
4697 }
4698 exact_object_shape(&document.root, &["type", "index"], &["type", "index"])
4699 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4700 let index = event_index(&document)?;
4701 if blocks.remove(&index).is_none() {
4702 return Err(codec_error(
4703 CodecErrorCode::InternalCoverageFailure,
4704 CodecPhase::SseReplay,
4705 ));
4706 }
4707 }
4708 "message_delta" => {
4709 if lifecycle != MessageLifecycle::InMessage || !blocks.is_empty() {
4710 return Err(codec_error(
4711 CodecErrorCode::InternalCoverageFailure,
4712 CodecPhase::SseReplay,
4713 ));
4714 }
4715 prove_message_delta_shape(&document.root)
4716 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4717 }
4718 "message_stop" => {
4719 if lifecycle != MessageLifecycle::InMessage || !blocks.is_empty() {
4720 return Err(codec_error(
4721 CodecErrorCode::InternalCoverageFailure,
4722 CodecPhase::SseReplay,
4723 ));
4724 }
4725 exact_object_shape(&document.root, &["type"], &["type"])
4726 .map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
4727 lifecycle = MessageLifecycle::MessageStopped;
4728 }
4729 _ => {
4730 return Err(codec_error(
4731 CodecErrorCode::InternalCoverageFailure,
4732 CodecPhase::SseReplay,
4733 ));
4734 }
4735 }
4736 }
4737 if reconstructed != output
4738 || lifecycle != MessageLifecycle::MessageStopped
4739 || !blocks.is_empty()
4740 {
4741 return Err(codec_error(
4742 CodecErrorCode::InternalCoverageFailure,
4743 CodecPhase::SseReplay,
4744 ));
4745 }
4746 Ok(occurrence_count)
4747}
4748
4749fn exact_object_shape(
4750 node: &JsonNode,
4751 allowed: &[&str],
4752 required: &[&str],
4753) -> Result<(), CodecErrorCode> {
4754 let JsonKind::Object(members) = &node.kind else {
4755 return Err(CodecErrorCode::InternalCoverageFailure);
4756 };
4757 if members
4758 .iter()
4759 .any(|member| !allowed.contains(&member.key.value.as_str()))
4760 || required
4761 .iter()
4762 .any(|required| !members.iter().any(|member| member.key.value == *required))
4763 {
4764 return Err(CodecErrorCode::InternalCoverageFailure);
4765 }
4766 Ok(())
4767}
4768
4769fn object_value<'a>(node: &'a JsonNode, key: &str) -> Result<&'a JsonNode, CodecErrorCode> {
4770 let JsonKind::Object(members) = &node.kind else {
4771 return Err(CodecErrorCode::InternalCoverageFailure);
4772 };
4773 members
4774 .iter()
4775 .find(|member| member.key.value == key)
4776 .map(|member| &member.value)
4777 .ok_or(CodecErrorCode::InternalCoverageFailure)
4778}
4779
4780fn prove_response_root_shape(node: &JsonNode) -> Result<(), CodecErrorCode> {
4781 const KEYS: &[&str] = &[
4782 "id",
4783 "type",
4784 "role",
4785 "model",
4786 "content",
4787 "stop_reason",
4788 "stop_sequence",
4789 "usage",
4790 ];
4791 exact_object_shape(node, KEYS, KEYS)?;
4792 if object_string(node, "type")? != "message"
4793 || object_string(node, "role")? != "assistant"
4794 || object_string(node, "id")?.is_empty()
4795 || object_string(node, "model")?.is_empty()
4796 {
4797 return Err(CodecErrorCode::InternalCoverageFailure);
4798 }
4799 prove_optional_enum_string(
4800 object_value(node, "stop_reason")?,
4801 &[
4802 "end_turn",
4803 "max_tokens",
4804 "stop_sequence",
4805 "tool_use",
4806 "pause_turn",
4807 "refusal",
4808 "model_context_window_exceeded",
4809 ],
4810 )?;
4811 prove_optional_string(object_value(node, "stop_sequence")?)?;
4812 let content = object_value(node, "content")?;
4813 let JsonKind::Array(blocks) = &content.kind else {
4814 return Err(CodecErrorCode::InternalCoverageFailure);
4815 };
4816 for block in blocks {
4817 prove_response_block_shape(block)?;
4818 }
4819 prove_usage_shape(object_value(node, "usage")?, true)
4820}
4821
4822fn prove_response_block_shape(node: &JsonNode) -> Result<(), CodecErrorCode> {
4823 match object_string(node, "type")? {
4824 "text" => exact_object_shape(node, &["type", "text", "citations"], &["type", "text"]),
4825 "tool_use" => exact_object_shape(
4826 node,
4827 &["type", "id", "name", "input"],
4828 &["type", "id", "name", "input"],
4829 ),
4830 "tool_result" => exact_object_shape(
4831 node,
4832 &["type", "tool_use_id", "content", "is_error"],
4833 &["type", "tool_use_id", "content"],
4834 ),
4835 "search_result" | "document" => exact_object_shape(
4836 node,
4837 &[
4838 "type", "title", "context", "text", "content", "source", "id",
4839 ],
4840 &["type"],
4841 ),
4842 "thinking" => exact_object_shape(
4843 node,
4844 &["type", "thinking", "signature"],
4845 &["type", "thinking", "signature"],
4846 ),
4847 "redacted_thinking" => exact_object_shape(node, &["type", "data"], &["type", "data"]),
4848 _ => Err(CodecErrorCode::InternalCoverageFailure),
4849 }
4850}
4851
4852fn prove_sse_block_start_shape(node: &JsonNode) -> Result<BlockKind, CodecErrorCode> {
4853 let kind = match object_string(node, "type")? {
4854 "text" => {
4855 exact_object_shape(node, &["type", "text"], &["type", "text"])?;
4856 BlockKind::Text
4857 }
4858 "tool_use" => {
4859 exact_object_shape(
4860 node,
4861 &["type", "id", "name", "input"],
4862 &["type", "id", "name", "input"],
4863 )?;
4864 BlockKind::InputJson
4865 }
4866 "thinking" => {
4867 exact_object_shape(
4868 node,
4869 &["type", "thinking", "signature"],
4870 &["type", "thinking"],
4871 )?;
4872 BlockKind::Thinking
4873 }
4874 "redacted_thinking" => {
4875 exact_object_shape(node, &["type", "data"], &["type", "data"])?;
4876 BlockKind::RedactedThinking
4877 }
4878 _ => return Err(CodecErrorCode::InternalCoverageFailure),
4879 };
4880 Ok(kind)
4881}
4882
4883fn prove_sse_delta_shape(
4884 node: &JsonNode,
4885 kind: BlockKind,
4886 limits: CodecLimits,
4887) -> Result<(), CodecErrorCode> {
4888 match (kind, object_string(node, "type")?) {
4889 (BlockKind::Text, "text_delta") => {
4890 exact_object_shape(node, &["type", "text"], &["type", "text"])
4891 }
4892 (BlockKind::Text, "citations_delta") => {
4893 exact_object_shape(node, &["type", "citation"], &["type", "citation"])
4894 }
4895 (BlockKind::InputJson, "input_json_delta") => {
4896 exact_object_shape(node, &["type", "partial_json"], &["type", "partial_json"])?;
4897 prove_rebuilt_partial_json(object_string(node, "partial_json")?, limits)
4898 }
4899 (BlockKind::Thinking, "thinking_delta") => {
4900 exact_object_shape(node, &["type", "thinking"], &["type", "thinking"])
4901 }
4902 (BlockKind::Thinking | BlockKind::RedactedThinking, "signature_delta") => {
4903 exact_object_shape(node, &["type", "signature"], &["type", "signature"])
4904 }
4905 _ => Err(CodecErrorCode::InternalCoverageFailure),
4906 }
4907}
4908
4909fn prove_rebuilt_partial_json(
4910 partial_json: &str,
4911 limits: CodecLimits,
4912) -> Result<(), CodecErrorCode> {
4913 let document = parse_json(partial_json.as_bytes(), limits, CodecPhase::SseReplay)
4914 .map_err(|error| error.code())?;
4915 let mut ledger = CoverageLedger::new(document.occurrence_count);
4916 account_subtree(&document.root, &mut ledger)?;
4917 ledger.finish()?;
4918 prove_final_mutable_shape(&document.root)?;
4919 let (reproved, _) = write_document(&document, partial_json.as_bytes())?;
4920 if reproved == partial_json.as_bytes() {
4921 Ok(())
4922 } else {
4923 Err(CodecErrorCode::InternalCoverageFailure)
4924 }
4925}
4926
4927fn prove_final_mutable_shape(node: &JsonNode) -> Result<(), CodecErrorCode> {
4928 match &node.kind {
4929 JsonKind::Object(members) => {
4930 for member in members {
4931 prove_final_mutable_shape(&member.value)?;
4932 }
4933 Ok(())
4934 }
4935 JsonKind::Array(values) => {
4936 for value in values {
4937 prove_final_mutable_shape(value)?;
4938 }
4939 Ok(())
4940 }
4941 JsonKind::String(_) | JsonKind::Bool(_) | JsonKind::Null => Ok(()),
4942 JsonKind::Number(_) => Err(CodecErrorCode::InternalCoverageFailure),
4943 }
4944}
4945
4946fn prove_message_delta_shape(node: &JsonNode) -> Result<(), CodecErrorCode> {
4947 exact_object_shape(
4948 node,
4949 &["type", "delta", "usage"],
4950 &["type", "delta", "usage"],
4951 )?;
4952 exact_object_shape(
4953 object_value(node, "delta")?,
4954 &["stop_reason", "stop_sequence"],
4955 &["stop_reason", "stop_sequence"],
4956 )?;
4957 let delta = object_value(node, "delta")?;
4958 prove_optional_enum_string(
4959 object_value(delta, "stop_reason")?,
4960 &[
4961 "end_turn",
4962 "max_tokens",
4963 "stop_sequence",
4964 "tool_use",
4965 "pause_turn",
4966 "refusal",
4967 "model_context_window_exceeded",
4968 ],
4969 )?;
4970 prove_optional_string(object_value(delta, "stop_sequence")?)?;
4971 prove_usage_shape(object_value(node, "usage")?, false)
4972}
4973
4974fn prove_optional_enum_string(node: &JsonNode, allowed: &[&str]) -> Result<(), CodecErrorCode> {
4975 match &node.kind {
4976 JsonKind::String(value) if allowed.contains(&value.value.as_str()) => Ok(()),
4977 JsonKind::Null => Ok(()),
4978 _ => Err(CodecErrorCode::InternalCoverageFailure),
4979 }
4980}
4981
4982fn prove_optional_string(node: &JsonNode) -> Result<(), CodecErrorCode> {
4983 if matches!(&node.kind, JsonKind::String(_) | JsonKind::Null) {
4984 Ok(())
4985 } else {
4986 Err(CodecErrorCode::InternalCoverageFailure)
4987 }
4988}
4989
4990fn prove_usage_shape(node: &JsonNode, require_input_tokens: bool) -> Result<(), CodecErrorCode> {
4991 const KEYS: &[&str] = &[
4992 "input_tokens",
4993 "output_tokens",
4994 "cache_creation_input_tokens",
4995 "cache_read_input_tokens",
4996 ];
4997 let required: &[&str] = if require_input_tokens {
4998 &["input_tokens", "output_tokens"]
4999 } else {
5000 &["output_tokens"]
5001 };
5002 exact_object_shape(node, KEYS, required)?;
5003 let JsonKind::Object(members) = &node.kind else {
5004 return Err(CodecErrorCode::InternalCoverageFailure);
5005 };
5006 if members.iter().any(|member| {
5007 !matches!(&member.value.kind, JsonKind::Number(raw) if !raw.starts_with('-') && !raw.contains(['.', 'e', 'E']))
5008 }) {
5009 return Err(CodecErrorCode::InternalCoverageFailure);
5010 }
5011 Ok(())
5012}
5013
5014fn validate_simple_event(document: &JsonDocument, expected: &str) -> Result<(), CodecError> {
5015 let JsonKind::Object(members) = &document.root.kind else {
5016 return Err(codec_error(
5017 CodecErrorCode::InvalidSseFrame,
5018 CodecPhase::SseDecode,
5019 ));
5020 };
5021 if members.len() != 1 || object_string(&document.root, "type") != Ok(expected) {
5022 return Err(codec_error(
5023 CodecErrorCode::InvalidSseFrame,
5024 CodecPhase::SseDecode,
5025 ));
5026 }
5027 validate_full_coverage(document)
5028}
5029
5030fn validate_full_coverage(document: &JsonDocument) -> Result<(), CodecError> {
5031 let mut ledger = CoverageLedger::new(document.occurrence_count);
5032 account_subtree(&document.root, &mut ledger)
5033 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5034 ledger
5035 .finish()
5036 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))
5037}
5038
5039fn transform_message_start(
5040 document: &mut JsonDocument,
5041 input: &[u8],
5042 ctx: &mut ResponseTransformContext<'_>,
5043) -> Result<(), CodecError> {
5044 let mut ledger = CoverageLedger::new(document.occurrence_count);
5045 ledger
5046 .mark(document.root.occurrence)
5047 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5048 let JsonKind::Object(members) = &mut document.root.kind else {
5049 return Err(codec_error(
5050 CodecErrorCode::InvalidSseFrame,
5051 CodecPhase::SseLifecycle,
5052 ));
5053 };
5054 let mut saw_message = false;
5055 for member in members {
5056 ledger
5057 .mark(member.key_occurrence)
5058 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5059 match member.key.value.as_str() {
5060 "type" => response_string_control(&mut member.value, ctx, &mut ledger),
5061 "message" => {
5062 saw_message = true;
5063 transform_response_root(&mut member.value, input, ctx, &mut ledger)
5064 }
5065 _ => Err(CodecErrorCode::UnsupportedResponseSurface),
5066 }
5067 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5068 }
5069 if !saw_message {
5070 return Err(codec_error(
5071 CodecErrorCode::InvalidSseFrame,
5072 CodecPhase::SseLifecycle,
5073 ));
5074 }
5075 ledger
5076 .finish()
5077 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))
5078}
5079
5080fn event_index(document: &JsonDocument) -> Result<u32, CodecError> {
5081 let JsonKind::Object(members) = &document.root.kind else {
5082 return Err(codec_error(
5083 CodecErrorCode::InvalidContentBlockIndex,
5084 CodecPhase::SseLifecycle,
5085 ));
5086 };
5087 let index = members
5088 .iter()
5089 .find(|member| member.key.value == "index")
5090 .ok_or_else(|| {
5091 codec_error(
5092 CodecErrorCode::InvalidContentBlockIndex,
5093 CodecPhase::SseLifecycle,
5094 )
5095 })?;
5096 let JsonKind::Number(raw) = &index.value.kind else {
5097 return Err(codec_error(
5098 CodecErrorCode::InvalidContentBlockIndex,
5099 CodecPhase::SseLifecycle,
5100 ));
5101 };
5102 if raw.starts_with('-') || raw.contains(['.', 'e', 'E']) {
5103 return Err(codec_error(
5104 CodecErrorCode::InvalidContentBlockIndex,
5105 CodecPhase::SseLifecycle,
5106 ));
5107 }
5108 raw.parse().map_err(|_| {
5109 codec_error(
5110 CodecErrorCode::InvalidContentBlockIndex,
5111 CodecPhase::SseLifecycle,
5112 )
5113 })
5114}
5115
5116fn transform_block_start(
5117 document: &mut JsonDocument,
5118 input: &[u8],
5119 raw_frame: &[u8],
5120 start_slot: usize,
5121 ctx: &mut ResponseTransformContext<'_>,
5122) -> Result<BlockAccumulator, CodecError> {
5123 let mut ledger = CoverageLedger::new(document.occurrence_count);
5124 ledger
5125 .mark(document.root.occurrence)
5126 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5127 let JsonKind::Object(members) = &mut document.root.kind else {
5128 return Err(codec_error(
5129 CodecErrorCode::InvalidSseFrame,
5130 CodecPhase::SseLifecycle,
5131 ));
5132 };
5133 let mut content_block = None;
5134 for member in members {
5135 ledger
5136 .mark(member.key_occurrence)
5137 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5138 match member.key.value.as_str() {
5139 "type" => response_string_control(&mut member.value, ctx, &mut ledger),
5140 "index" => {
5141 ledger
5142 .mark(member.value.occurrence)
5143 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5144 if matches!(member.value.kind, JsonKind::Number(_)) {
5145 Ok(())
5146 } else {
5147 Err(CodecErrorCode::InvalidContentBlockIndex)
5148 }
5149 }
5150 "content_block" => {
5151 content_block = Some(&mut member.value);
5152 Ok(())
5153 }
5154 _ => Err(CodecErrorCode::InvalidSseFrame),
5155 }
5156 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5157 }
5158 let block = content_block
5159 .ok_or_else(|| codec_error(CodecErrorCode::InvalidSseFrame, CodecPhase::SseLifecycle))?;
5160 let block_type = object_string(block, "type")
5161 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?
5162 .to_owned();
5163 let mut logical = String::new();
5164 let mut signature = String::new();
5165 let kind = match block_type.as_str() {
5166 "text" => {
5167 ledger
5168 .mark(block.occurrence)
5169 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5170 let JsonKind::Object(block_members) = &mut block.kind else {
5171 return Err(codec_error(
5172 CodecErrorCode::InvalidSseFrame,
5173 CodecPhase::SseLifecycle,
5174 ));
5175 };
5176 let mut saw_text = false;
5177 for member in block_members {
5178 ledger
5179 .mark(member.key_occurrence)
5180 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5181 match member.key.value.as_str() {
5182 "type" => {
5183 response_enum_string_control(&mut member.value, &["text"], ctx, &mut ledger)
5184 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?
5185 }
5186 "text" => {
5187 ledger
5188 .mark(member.value.occurrence)
5189 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5190 let JsonKind::String(value) = &mut member.value.kind else {
5191 return Err(codec_error(
5192 CodecErrorCode::InvalidSseFrame,
5193 CodecPhase::SseLifecycle,
5194 ));
5195 };
5196 logical.push_str(&value.value);
5197 value.value.clear();
5198 saw_text = true;
5199 }
5200 _ => {
5201 return Err(codec_error(
5202 CodecErrorCode::InvalidSseFrame,
5203 CodecPhase::SseLifecycle,
5204 ));
5205 }
5206 }
5207 }
5208 if !saw_text {
5209 return Err(codec_error(
5210 CodecErrorCode::InvalidSseFrame,
5211 CodecPhase::SseLifecycle,
5212 ));
5213 }
5214 BlockKind::Text
5215 }
5216 "tool_use" => {
5217 ledger
5218 .mark(block.occurrence)
5219 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5220 let JsonKind::Object(block_members) = &mut block.kind else {
5221 return Err(codec_error(
5222 CodecErrorCode::InvalidSseFrame,
5223 CodecPhase::SseLifecycle,
5224 ));
5225 };
5226 let mut saw_id = false;
5227 let mut saw_name = false;
5228 let mut saw_input = false;
5229 for member in block_members {
5230 ledger
5231 .mark(member.key_occurrence)
5232 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5233 match member.key.value.as_str() {
5234 "type" => response_enum_string_control(
5235 &mut member.value,
5236 &["tool_use"],
5237 ctx,
5238 &mut ledger,
5239 )
5240 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?,
5241 "id" => {
5242 response_nonempty_string_control(&mut member.value, ctx, &mut ledger)
5243 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5244 saw_id = true;
5245 }
5246 "name" => {
5247 response_nonempty_string_control(&mut member.value, ctx, &mut ledger)
5248 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5249 saw_name = true;
5250 }
5251 "input" => {
5252 if !matches!(&member.value.kind, JsonKind::Object(values) if values.is_empty())
5253 {
5254 return Err(codec_error(
5255 CodecErrorCode::InvalidContentBlockDelta,
5256 CodecPhase::SseLifecycle,
5257 ));
5258 }
5259 account_subtree(&member.value, &mut ledger)
5260 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5261 saw_input = true;
5262 }
5263 _ => {
5264 return Err(codec_error(
5265 CodecErrorCode::InvalidSseFrame,
5266 CodecPhase::SseLifecycle,
5267 ));
5268 }
5269 }
5270 }
5271 if !saw_id || !saw_name || !saw_input {
5272 return Err(codec_error(
5273 CodecErrorCode::InvalidSseFrame,
5274 CodecPhase::SseLifecycle,
5275 ));
5276 }
5277 BlockKind::InputJson
5278 }
5279 "thinking" | "redacted_thinking" => {
5280 let (initial_logical, initial_signature) =
5281 validate_signed_sse_block(block, &block_type, ctx)?;
5282 logical = initial_logical;
5283 signature = initial_signature;
5284 account_subtree(block, &mut ledger)
5285 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5286 if block_type == "thinking" {
5287 BlockKind::Thinking
5288 } else {
5289 BlockKind::RedactedThinking
5290 }
5291 }
5292 _ => {
5293 return Err(codec_error(
5294 CodecErrorCode::UnsupportedResponseSurface,
5295 CodecPhase::SseLifecycle,
5296 ));
5297 }
5298 };
5299 ledger
5300 .finish()
5301 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5302 let start_frame = if matches!(kind, BlockKind::Thinking | BlockKind::RedactedThinking) {
5303 raw_frame.to_vec()
5304 } else {
5305 canonical_sse_frame("content_block_start", document, input)?.0
5306 };
5307 let mut signed_frames = Vec::new();
5308 if matches!(kind, BlockKind::Thinking | BlockKind::RedactedThinking) {
5309 signed_frames.push((start_slot, raw_frame.to_vec()));
5310 }
5311 Ok(BlockAccumulator {
5312 kind,
5313 start_slot,
5314 start_frame,
5315 delta_slots: Vec::new(),
5316 logical,
5317 signature,
5318 signed_frames,
5319 passthrough_frames: Vec::new(),
5320 })
5321}
5322
5323fn validate_signed_sse_block(
5324 block: &JsonNode,
5325 block_type: &str,
5326 ctx: &mut ResponseTransformContext<'_>,
5327) -> Result<(String, String), CodecError> {
5328 let JsonKind::Object(members) = &block.kind else {
5329 return Err(codec_error(
5330 CodecErrorCode::SignedSurfaceMalformed,
5331 CodecPhase::SseLifecycle,
5332 ));
5333 };
5334 let mut logical = String::new();
5335 let mut signature = String::new();
5336 let mut has_payload = false;
5337 let mut has_type = false;
5338 for member in members {
5339 match member.key.value.as_str() {
5340 "type" if string_value(&member.value) == Ok(block_type) => {
5341 has_type = true;
5342 }
5343 "thinking" if block_type == "thinking" => {
5344 let value = string_value(&member.value)
5345 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5346 guard_mutable_text_response(value)
5347 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5348 let _ = ctx
5349 .restore(value)
5350 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5351 ctx.validate(value, &[])
5352 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5353 logical.push_str(value);
5354 has_payload = true;
5355 }
5356 "signature" if block_type == "thinking" => {
5357 let value = string_value(&member.value)
5358 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5359 let _ = ctx
5360 .restore(value)
5361 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5362 signature.push_str(value);
5363 }
5364 "data" if block_type == "redacted_thinking" => {
5365 let value = string_value(&member.value)
5366 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5367 let _ = ctx
5368 .restore(value)
5369 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5370 has_payload = true;
5371 }
5372 _ => {
5373 return Err(codec_error(
5374 CodecErrorCode::SignedSurfaceMalformed,
5375 CodecPhase::SseLifecycle,
5376 ));
5377 }
5378 }
5379 }
5380 if !has_type || !has_payload {
5381 return Err(codec_error(
5382 CodecErrorCode::SignedSurfaceMalformed,
5383 CodecPhase::SseLifecycle,
5384 ));
5385 }
5386 Ok((logical, signature))
5387}
5388
5389fn transform_block_delta(
5390 accumulator: &mut BlockAccumulator,
5391 document: &mut JsonDocument,
5392 input: &[u8],
5393 raw_frame: &[u8],
5394 slot: usize,
5395 ctx: &mut ResponseTransformContext<'_>,
5396) -> Result<(), CodecError> {
5397 let mut ledger = CoverageLedger::new(document.occurrence_count);
5398 ledger
5399 .mark(document.root.occurrence)
5400 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5401 let JsonKind::Object(members) = &mut document.root.kind else {
5402 return Err(codec_error(
5403 CodecErrorCode::InvalidSseFrame,
5404 CodecPhase::SseLifecycle,
5405 ));
5406 };
5407 let mut delta = None;
5408 for member in members {
5409 ledger
5410 .mark(member.key_occurrence)
5411 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5412 match member.key.value.as_str() {
5413 "type" => response_string_control(&mut member.value, ctx, &mut ledger)
5414 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?,
5415 "index" => account_subtree(&member.value, &mut ledger)
5416 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?,
5417 "delta" => delta = Some(&mut member.value),
5418 _ => {
5419 return Err(codec_error(
5420 CodecErrorCode::InvalidSseFrame,
5421 CodecPhase::SseLifecycle,
5422 ));
5423 }
5424 }
5425 }
5426 let delta = delta
5427 .ok_or_else(|| codec_error(CodecErrorCode::InvalidSseFrame, CodecPhase::SseLifecycle))?;
5428 let delta_type = object_string(delta, "type")
5429 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?
5430 .to_owned();
5431 if delta_type == "citations_delta" {
5432 if !matches!(accumulator.kind, BlockKind::Text) {
5433 return Err(codec_error(
5434 CodecErrorCode::InvalidContentBlockDelta,
5435 CodecPhase::SseLifecycle,
5436 ));
5437 }
5438 transform_response_citation_delta(delta, ctx, &mut ledger)
5439 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5440 ledger
5441 .finish()
5442 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5443 let (bytes, provenance) = canonical_sse_frame("content_block_delta", document, input)?;
5444 accumulator
5445 .passthrough_frames
5446 .push((slot, bytes, provenance));
5447 return Ok(());
5448 }
5449 account_subtree(delta, &mut ledger)
5450 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5451 let value = match delta_type.as_str() {
5452 "text_delta" => object_string(delta, "text"),
5453 "input_json_delta" => object_string(delta, "partial_json"),
5454 "thinking_delta" => object_string(delta, "thinking"),
5455 "signature_delta" => object_string(delta, "signature"),
5456 _ => Err(CodecErrorCode::InvalidContentBlockDelta),
5457 }
5458 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5459 match (&accumulator.kind, delta_type.as_str()) {
5460 (BlockKind::Text, "text_delta") | (BlockKind::InputJson, "input_json_delta") => {
5461 accumulator.logical.push_str(value);
5462 accumulator.delta_slots.push(slot);
5463 }
5464 (BlockKind::Thinking, "thinking_delta") => {
5465 accumulator.logical.push_str(value);
5466 accumulator.signed_frames.push((slot, raw_frame.to_vec()));
5467 }
5468 (BlockKind::Thinking, "signature_delta") => {
5469 accumulator.signature.push_str(value);
5470 accumulator.signed_frames.push((slot, raw_frame.to_vec()));
5471 }
5472 (BlockKind::RedactedThinking, "signature_delta") => {
5473 accumulator.signature.push_str(value);
5474 accumulator.signed_frames.push((slot, raw_frame.to_vec()));
5475 }
5476 _ => {
5477 return Err(codec_error(
5478 CodecErrorCode::InvalidContentBlockDelta,
5479 CodecPhase::SseLifecycle,
5480 ));
5481 }
5482 }
5483 if accumulator.logical.len() > ctx.limits().max_sse_accumulator_bytes()
5484 || accumulator.signature.len() > ctx.limits().max_sse_accumulator_bytes()
5485 {
5486 return Err(codec_error(
5487 CodecErrorCode::SseAccumulatorLimitExceeded,
5488 CodecPhase::SseLifecycle,
5489 ));
5490 }
5491 ledger
5492 .finish()
5493 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))
5494}
5495
5496fn transform_response_citation_delta(
5497 delta: &mut JsonNode,
5498 ctx: &mut ResponseTransformContext<'_>,
5499 ledger: &mut CoverageLedger,
5500) -> Result<(), CodecErrorCode> {
5501 ledger.mark(delta.occurrence)?;
5502 let JsonKind::Object(members) = &mut delta.kind else {
5503 return Err(CodecErrorCode::InvalidContentBlockDelta);
5504 };
5505 for member in members {
5506 ledger.mark(member.key_occurrence)?;
5507 match member.key.value.as_str() {
5508 "type" => response_string_control(&mut member.value, ctx, ledger)?,
5509 "citation" => transform_response_citation(&mut member.value, ctx, ledger)?,
5510 _ => return Err(CodecErrorCode::InvalidContentBlockDelta),
5511 }
5512 }
5513 Ok(())
5514}
5515
5516struct StopFrame<'document, 'input> {
5517 slot: usize,
5518 document: &'document mut JsonDocument,
5519 input: &'input [u8],
5520}
5521
5522fn finish_block(
5523 index: u32,
5524 accumulator: BlockAccumulator,
5525 slots: &mut [ReplaySlot],
5526 stop: StopFrame<'_, '_>,
5527 ctx: &mut ResponseTransformContext<'_>,
5528 concatenated_text: &mut String,
5529 concatenated_authorized: &mut Vec<Range<usize>>,
5530) -> Result<(), CodecError> {
5531 validate_stop_event(stop.document)?;
5532 let (stop_frame, stop_provenance) =
5533 canonical_sse_frame("content_block_stop", stop.document, stop.input)?;
5534 slots[stop.slot] = ReplaySlot {
5535 bytes: Some(stop_frame),
5536 provenance: stop_provenance,
5537 };
5538 match accumulator.kind {
5539 BlockKind::Text => {
5540 let restored = ctx
5541 .restore(&accumulator.logical)
5542 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5543 guard_mutable_text_response(&restored.text)
5544 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5545 ctx.validate(&restored.text, &restored.authorized_output_ranges)
5546 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5547 let base = concatenated_text.len();
5548 concatenated_text.push_str(&restored.text);
5549 concatenated_authorized.extend(
5550 restored
5551 .authorized_output_ranges
5552 .iter()
5553 .map(|range| base + range.start..base + range.end),
5554 );
5555 slots[accumulator.start_slot].bytes = Some(accumulator.start_frame);
5556 if let Some(first) = accumulator.delta_slots.first().copied() {
5557 let (bytes, provenance) = built_delta_frame(
5558 index,
5559 "text_delta",
5560 "text",
5561 &restored.text,
5562 restored.authorized_output_ranges,
5563 );
5564 slots[first] = ReplaySlot {
5565 bytes: Some(bytes),
5566 provenance,
5567 };
5568 } else if !restored.text.is_empty() {
5569 return Err(codec_error(
5570 CodecErrorCode::InvalidContentBlockDelta,
5571 CodecPhase::SseLifecycle,
5572 ));
5573 }
5574 for (slot, bytes, provenance) in accumulator.passthrough_frames {
5575 slots[slot] = ReplaySlot {
5576 bytes: Some(bytes),
5577 provenance,
5578 };
5579 }
5580 }
5581 BlockKind::InputJson => {
5582 let partial_input =
5583 if accumulator.delta_slots.is_empty() && accumulator.logical.is_empty() {
5584 "{}"
5585 } else {
5586 accumulator.logical.as_str()
5587 };
5588 let mut partial = parse_json(
5589 partial_input.as_bytes(),
5590 ctx.limits(),
5591 CodecPhase::SseLifecycle,
5592 )?;
5593 let mut ledger = CoverageLedger::new(partial.occurrence_count);
5594 transform_response_mutable(&mut partial.root, ctx, &mut ledger)
5595 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5596 ledger
5597 .finish()
5598 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5599 let (partial_bytes, partial_provenance) =
5600 write_document(&partial, partial_input.as_bytes())
5601 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5602 let partial_text = String::from_utf8(partial_bytes)
5603 .map_err(|_| codec_error(CodecErrorCode::InvalidUtf8, CodecPhase::SseLifecycle))?;
5604 slots[accumulator.start_slot].bytes = Some(accumulator.start_frame);
5605 if let Some(first) = accumulator.delta_slots.first().copied() {
5606 let (bytes, provenance) = built_delta_frame(
5607 index,
5608 "input_json_delta",
5609 "partial_json",
5610 &partial_text,
5611 partial_provenance.authorized_output_ranges().to_vec(),
5612 );
5613 slots[first] = ReplaySlot {
5614 bytes: Some(bytes),
5615 provenance,
5616 };
5617 } else if partial_text != "{}" {
5618 return Err(codec_error(
5619 CodecErrorCode::InvalidContentBlockDelta,
5620 CodecPhase::SseLifecycle,
5621 ));
5622 }
5623 }
5624 BlockKind::Thinking => {
5625 guard_mutable_text_response(&accumulator.logical)
5626 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5627 let _ = ctx
5628 .restore(&accumulator.logical)
5629 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5630 ctx.validate(&accumulator.logical, &[])
5631 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5632 let _ = ctx
5633 .restore(&accumulator.signature)
5634 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5635 for (slot, raw) in accumulator.signed_frames {
5636 let mut provenance = OutputProvenance::default();
5637 provenance.record_signed(0..raw.len());
5638 slots[slot] = ReplaySlot {
5639 bytes: Some(raw),
5640 provenance,
5641 };
5642 }
5643 }
5644 BlockKind::RedactedThinking => {
5645 let _ = ctx
5646 .restore(&accumulator.signature)
5647 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5648 for (slot, raw) in accumulator.signed_frames {
5649 let mut provenance = OutputProvenance::default();
5650 provenance.record_signed(0..raw.len());
5651 slots[slot] = ReplaySlot {
5652 bytes: Some(raw),
5653 provenance,
5654 };
5655 }
5656 }
5657 }
5658 Ok(())
5659}
5660
5661fn validate_stop_event(document: &JsonDocument) -> Result<(), CodecError> {
5662 let JsonKind::Object(members) = &document.root.kind else {
5663 return Err(codec_error(
5664 CodecErrorCode::InvalidSseFrame,
5665 CodecPhase::SseLifecycle,
5666 ));
5667 };
5668 let valid = members.len() == 2
5669 && members.iter().any(|member| member.key.value == "type")
5670 && members.iter().any(|member| member.key.value == "index");
5671 if valid {
5672 validate_full_coverage(document)
5673 } else {
5674 Err(codec_error(
5675 CodecErrorCode::InvalidSseFrame,
5676 CodecPhase::SseLifecycle,
5677 ))
5678 }
5679}
5680
5681fn transform_message_delta(
5682 document: &mut JsonDocument,
5683 ctx: &mut ResponseTransformContext<'_>,
5684) -> Result<(), CodecError> {
5685 let mut ledger = CoverageLedger::new(document.occurrence_count);
5686 ledger
5687 .mark(document.root.occurrence)
5688 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5689 let JsonKind::Object(members) = &mut document.root.kind else {
5690 return Err(codec_error(
5691 CodecErrorCode::InvalidSseFrame,
5692 CodecPhase::SseLifecycle,
5693 ));
5694 };
5695 let mut saw_delta = false;
5696 let mut saw_usage = false;
5697 for member in members {
5698 ledger
5699 .mark(member.key_occurrence)
5700 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5701 match member.key.value.as_str() {
5702 "type" => response_string_control(&mut member.value, ctx, &mut ledger)
5703 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?,
5704 "delta" => {
5705 transform_message_delta_controls(&mut member.value, ctx, &mut ledger)
5706 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5707 saw_delta = true;
5708 }
5709 "usage" => {
5710 response_usage(&mut member.value, &mut ledger, false)
5711 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))?;
5712 saw_usage = true;
5713 }
5714 _ => {
5715 return Err(codec_error(
5716 CodecErrorCode::InvalidSseFrame,
5717 CodecPhase::SseLifecycle,
5718 ));
5719 }
5720 }
5721 }
5722 if !saw_delta || !saw_usage {
5723 return Err(codec_error(
5724 CodecErrorCode::InvalidSseFrame,
5725 CodecPhase::SseLifecycle,
5726 ));
5727 }
5728 ledger
5729 .finish()
5730 .map_err(|code| codec_error(code, CodecPhase::SseLifecycle))
5731}
5732
5733fn transform_message_delta_controls(
5734 delta: &mut JsonNode,
5735 ctx: &mut ResponseTransformContext<'_>,
5736 ledger: &mut CoverageLedger,
5737) -> Result<(), CodecErrorCode> {
5738 ledger.mark(delta.occurrence)?;
5739 let JsonKind::Object(members) = &mut delta.kind else {
5740 return Err(CodecErrorCode::InvalidSseFrame);
5741 };
5742 let mut saw_reason = false;
5743 let mut saw_sequence = false;
5744 for member in members {
5745 ledger.mark(member.key_occurrence)?;
5746 match member.key.value.as_str() {
5747 "stop_reason" => {
5748 response_enum_string_or_null(
5749 &mut member.value,
5750 &[
5751 "end_turn",
5752 "max_tokens",
5753 "stop_sequence",
5754 "tool_use",
5755 "pause_turn",
5756 "refusal",
5757 "model_context_window_exceeded",
5758 ],
5759 ctx,
5760 ledger,
5761 )?;
5762 saw_reason = true;
5763 }
5764 "stop_sequence" => {
5765 response_string_or_null(&mut member.value, ctx, ledger)?;
5766 saw_sequence = true;
5767 }
5768 _ => return Err(CodecErrorCode::InvalidSseFrame),
5769 }
5770 }
5771 if saw_reason && saw_sequence {
5772 Ok(())
5773 } else {
5774 Err(CodecErrorCode::InvalidSseFrame)
5775 }
5776}
5777
5778fn validate_future_event(
5779 document: &JsonDocument,
5780 event: &str,
5781 ctx: &mut ResponseTransformContext<'_>,
5782) -> Result<(), CodecError> {
5783 let JsonKind::Object(members) = &document.root.kind else {
5784 return Err(codec_error(
5785 CodecErrorCode::UnsafeFutureEvent,
5786 CodecPhase::SseLifecycle,
5787 ));
5788 };
5789 validate_future_atom(event, ctx)?;
5790 for member in members {
5791 validate_future_atom(&member.key.value, ctx)?;
5792 match (&*member.key.value, &member.value.kind) {
5793 ("type", JsonKind::String(value)) if value.value == event => {}
5794 (_, JsonKind::Bool(_) | JsonKind::Null) => {}
5795 _ => {
5796 return Err(codec_error(
5797 CodecErrorCode::UnsafeFutureEvent,
5798 CodecPhase::SseLifecycle,
5799 ));
5800 }
5801 }
5802 }
5803 validate_full_coverage(document)
5804}
5805
5806fn validate_future_atom(
5807 value: &str,
5808 ctx: &mut ResponseTransformContext<'_>,
5809) -> Result<(), CodecError> {
5810 let unsafe_event = || codec_error(CodecErrorCode::UnsafeFutureEvent, CodecPhase::SseLifecycle);
5811 guard_mutable_text_response(value).map_err(|_| unsafe_event())?;
5812 let restored = ctx.restore(value).map_err(|_| unsafe_event())?;
5813 if restored.text != value || !restored.authorized_output_ranges.is_empty() {
5814 return Err(unsafe_event());
5815 }
5816 ctx.validate(value, &[]).map_err(|_| unsafe_event())
5817}
5818
5819fn canonical_sse_frame(
5820 event: &str,
5821 document: &JsonDocument,
5822 input: &[u8],
5823) -> Result<(Vec<u8>, OutputProvenance), CodecError> {
5824 let (json, json_provenance) =
5825 write_document(document, input).map_err(|code| codec_error(code, CodecPhase::SseReplay))?;
5826 let mut output = Vec::with_capacity(event.len() + json.len() + 16);
5827 output.extend_from_slice(b"event: ");
5828 output.extend_from_slice(event.as_bytes());
5829 output.extend_from_slice(b"\ndata: ");
5830 let offset = output.len();
5831 output.extend_from_slice(&json);
5832 output.extend_from_slice(b"\n\n");
5833 let mut provenance = OutputProvenance::default();
5834 for range in json_provenance.authorized_output_ranges() {
5835 provenance.record_authorized(offset + range.start..offset + range.end);
5836 }
5837 for range in json_provenance.signed_opaque_ranges() {
5838 provenance.record_signed(offset + range.start..offset + range.end);
5839 }
5840 Ok((output, provenance))
5841}
5842
5843fn built_delta_frame(
5844 index: u32,
5845 delta_type: &str,
5846 field: &str,
5847 value: &str,
5848 authorized: Vec<Range<usize>>,
5849) -> (Vec<u8>, OutputProvenance) {
5850 let mut output = format!(
5851 "event: content_block_delta\ndata: {{\"type\":\"content_block_delta\",\"index\":{index},\"delta\":{{\"type\":\"{delta_type}\",\"{field}\":"
5852 )
5853 .into_bytes();
5854 let mut provenance = OutputProvenance::default();
5855 write_json_string(
5856 &JsonString {
5857 value: value.to_owned(),
5858 authorized,
5859 },
5860 &mut output,
5861 &mut provenance,
5862 );
5863 output.extend_from_slice(b"}}\n\n");
5864 (output, provenance)
5865}
5866
5867#[cfg(test)]
5868mod inspection_parity_tests {
5869 use gaze::{Scope, Session};
5870
5871 use super::*;
5872 use crate::codec::ResponseResidualValidator;
5873
5874 struct CleanResidual;
5875
5876 impl ResponseResidualValidator for CleanResidual {
5877 fn validate(
5878 &mut self,
5879 _value: &str,
5880 _authorized_output_ranges: &[Range<usize>],
5881 ) -> Result<(), CodecErrorCode> {
5882 Ok(())
5883 }
5884 }
5885
5886 fn malformed_sse_error(inspection_configured: bool) -> CodecError {
5887 let session = Session::new(Scope::Ephemeral).unwrap();
5888 let snapshot = session.begin_transaction().commit().unwrap();
5889 let mut residual = CleanResidual;
5890 let mut context = ResponseTransformContext::new(
5891 &snapshot,
5892 &mut residual,
5893 WireFormat::Sse,
5894 CodecLimits::default(),
5895 );
5896 if inspection_configured {
5897 context.request_inspection_projection();
5898 }
5899 AnthropicMessagesCodec
5900 .restore_response(
5901 b"event: content_block_start\ndata: {\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n",
5902 &mut context,
5903 )
5904 .unwrap_err()
5905 }
5906
5907 #[test]
5908 fn malformed_sse_error_is_identical_with_and_without_inspection() {
5909 let without = malformed_sse_error(false);
5910 let with = malformed_sse_error(true);
5911 assert_eq!(with.code(), without.code());
5912 assert_eq!(with.phase(), without.phase());
5913 }
5914}