1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! The per-revision `CallToolResult` codecs, one module per revision.
//!
//! # Why one file per revision rather than a macro
//!
//! The revisions differ cumulatively, so the whole spread is one axis:
//!
//! | level | revision | adds |
//! | --- | --- | --- |
//! | 0 | 2024-11-05 | untyped `annotations`; no `meta` on blocks |
//! | 1 | 2025-03-26 | audio; typed `annotations` without `lastModified` |
//! | 2 | 2025-06-18 | resource links, `structuredContent`, `lastModified`, block `meta` |
//! | 3 | 2025-11-25 | `icons` on resource links |
//! | 4 | 2026-07-28, draft | `resultType`, and a *typed* `meta` |
//!
//! A macro over that table was tried and is the wrong tool. The content unions
//! are `#[serde(untagged)]` — the member index is the only thing telling audio
//! from image, and an off-by-one produces no error at all, just a block that
//! arrived as the wrong kind of thing. Generating the arms from a level trades
//! a visible, checkable repetition for an invisible index, and the repetition
//! is what a reader checks against the schema. Each file here is short enough
//! to read against the generated types it wraps.
//!
//! What is shared lives here rather than in each file: a constant duplicated
//! five times is a constant that will be wrong once.
//!
//! # The union is where the risk is
//!
//! serde takes the first member that round-trips and the JSON carries no
//! discriminant, so an image and an audio block — both a base64 string and a
//! media type, each structurally valid as the other — swap silently.
//! `crates/protocol-droid-schema/tests/content_member_order.rs` is the test
//! that catches it, by asserting which arm a real fixture re-serialises into.
//!
//! # Lowering makes no decisions of its own
//!
//! Every codec asks [`lower_tool_result`](crate::domain::ir::lower::lower_tool_result) and reports what came back. Whether
//! a value is expressible is the gate's answer and a downgrade is a
//! `DegradeRule`'s. A codec that formed a second opinion is how two revisions
//! start disagreeing about what they support.
pub use WireToolResults;
pub use verbatim;
/// The key `_meta` travels under in the IR's extension bag.
///
/// `CallToolResult` has had `_meta` since the first revision, so whether a
/// value can carry it is never a feature question — only a question of shape.
/// The IR has no `meta` field, so it rides in the bag and comes back out as a
/// field on the way down.
///
/// The wire key is `_meta` while the generated field is `meta`, because
/// `_meta` is not an identifier. A fixture that writes `meta` lands in the
/// catch-all `extra` instead, which is precisely the confusion the underscore
/// exists to prevent.
pub const META_KEY: &str = "_meta";
/// The `type` discriminants, per each revision's schema.
pub const TEXT_TYPE: &str = "text";
pub const IMAGE_TYPE: &str = "image";
pub const AUDIO_TYPE: &str = "audio";
pub const RESOURCE_LINK_TYPE: &str = "resource_link";
pub const RESOURCE_TYPE: &str = "resource";