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