Skip to main content

gproxy_protocol/protocol/claude/common/blocks/
content.rs

1use serde::{Deserialize, Serialize};
2
3use super::super::server_tools::*;
4use super::super::{JsonObject, MessageRole, StringOrArray, TypedObject};
5use super::*;
6use crate::protocol::extensible::type_tag_union_deserialize;
7
8pub type MessageContent = StringOrArray<ContentBlockParam>;
9pub type SystemPrompt = StringOrArray<TextBlock>;
10pub type ContentBlock = ResponseContentBlock;
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
13#[non_exhaustive]
14pub struct MessageParam {
15    pub role: MessageRole,
16    pub content: MessageContent,
17    #[serde(default, flatten, skip_serializing_if = "JsonObject::is_empty")]
18    pub extra: JsonObject,
19}
20
21// Deserialize is hand-rolled below: one `type` tag match instead of untagged
22// trials. Serialization stays derive(untagged) — each block struct carries its
23// own `type` witness field.
24#[derive(Debug, Clone, PartialEq, Serialize)]
25#[serde(untagged)]
26#[non_exhaustive]
27pub enum ContentBlockParam {
28    Text(TextBlock),
29    Image(ImageBlock),
30    Document(DocumentBlock),
31    SearchResult(SearchResultBlock),
32    Thinking(ThinkingBlock),
33    RedactedThinking(RedactedThinkingBlock),
34    ToolUse(ToolUseBlock),
35    ToolResult(ToolResultBlock),
36    ServerToolUse(ServerToolUseBlock),
37    WebSearchToolResult(WebSearchToolResultBlock),
38    WebFetchToolResult(WebFetchToolResultBlock),
39    AdvisorToolResult(AdvisorToolResultBlock),
40    CodeExecutionToolResult(CodeExecutionToolResultBlock),
41    BashCodeExecutionToolResult(BashCodeExecutionToolResultBlock),
42    TextEditorCodeExecutionToolResult(TextEditorCodeExecutionToolResultBlock),
43    ToolSearchToolResult(ToolSearchToolResultBlock),
44    McpToolUse(McpToolUseBlock),
45    McpToolResult(McpToolResultBlock),
46    ContainerUpload(ContainerUploadBlock),
47    Compaction(CompactionBlock),
48    MidConversationSystem(MidConversationSystemBlock),
49    ToolAddition(ToolAdditionBlock),
50    ToolRemoval(ToolRemovalBlock),
51    Fallback(FallbackBlock),
52    Raw(TypedObject),
53}
54
55type_tag_union_deserialize!(ContentBlockParam {
56    "text" => Text,
57    "image" => Image,
58    "document" => Document,
59    "search_result" => SearchResult,
60    "thinking" => Thinking,
61    "redacted_thinking" => RedactedThinking,
62    "tool_use" => ToolUse,
63    "tool_result" => ToolResult,
64    "server_tool_use" => ServerToolUse,
65    "web_search_tool_result" => WebSearchToolResult,
66    "web_fetch_tool_result" => WebFetchToolResult,
67    "advisor_tool_result" => AdvisorToolResult,
68    "code_execution_tool_result" => CodeExecutionToolResult,
69    "bash_code_execution_tool_result" => BashCodeExecutionToolResult,
70    "text_editor_code_execution_tool_result" => TextEditorCodeExecutionToolResult,
71    "tool_search_tool_result" => ToolSearchToolResult,
72    "mcp_tool_use" => McpToolUse,
73    "mcp_tool_result" => McpToolResult,
74    "container_upload" => ContainerUpload,
75    "compaction" => Compaction,
76    "mid_conv_system" => MidConversationSystem,
77    "tool_addition" => ToolAddition,
78    "tool_removal" => ToolRemoval,
79    "fallback" => Fallback,
80});
81
82#[derive(Debug, Clone, PartialEq, Serialize)]
83#[serde(untagged)]
84#[non_exhaustive]
85pub enum ResponseContentBlock {
86    Text(ResponseTextBlock),
87    Thinking(ThinkingBlock),
88    RedactedThinking(RedactedThinkingBlock),
89    ToolUse(ResponseToolUseBlock),
90    ServerToolUse(ResponseServerToolUseBlock),
91    WebSearchToolResult(ResponseWebSearchToolResultBlock),
92    WebFetchToolResult(ResponseWebFetchToolResultBlock),
93    AdvisorToolResult(ResponseAdvisorToolResultBlock),
94    CodeExecutionToolResult(ResponseCodeExecutionToolResultBlock),
95    BashCodeExecutionToolResult(ResponseBashCodeExecutionToolResultBlock),
96    TextEditorCodeExecutionToolResult(ResponseTextEditorCodeExecutionToolResultBlock),
97    ToolSearchToolResult(ResponseToolSearchToolResultBlock),
98    McpToolUse(ResponseMcpToolUseBlock),
99    McpToolResult(ResponseMcpToolResultBlock),
100    ContainerUpload(ResponseContainerUploadBlock),
101    Compaction(ResponseCompactionBlock),
102    Fallback(FallbackBlock),
103    Raw(TypedObject),
104}
105
106type_tag_union_deserialize!(ResponseContentBlock {
107    "text" => Text,
108    "thinking" => Thinking,
109    "redacted_thinking" => RedactedThinking,
110    "tool_use" => ToolUse,
111    "server_tool_use" => ServerToolUse,
112    "web_search_tool_result" => WebSearchToolResult,
113    "web_fetch_tool_result" => WebFetchToolResult,
114    "advisor_tool_result" => AdvisorToolResult,
115    "code_execution_tool_result" => CodeExecutionToolResult,
116    "bash_code_execution_tool_result" => BashCodeExecutionToolResult,
117    "text_editor_code_execution_tool_result" => TextEditorCodeExecutionToolResult,
118    "tool_search_tool_result" => ToolSearchToolResult,
119    "mcp_tool_use" => McpToolUse,
120    "mcp_tool_result" => McpToolResult,
121    "container_upload" => ContainerUpload,
122    "compaction" => Compaction,
123    "fallback" => Fallback,
124});
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127#[serde(untagged)]
128#[non_exhaustive]
129pub enum ToolResultContent {
130    Text(String),
131    Blocks(Vec<ToolResultContentBlock>),
132    Raw(serde_json::Value),
133}
134
135#[derive(Debug, Clone, PartialEq, Serialize)]
136#[serde(untagged)]
137#[non_exhaustive]
138pub enum ToolResultContentBlock {
139    Text(TextBlock),
140    Image(ImageBlock),
141    SearchResult(SearchResultBlock),
142    Document(DocumentBlock),
143    ToolReference(ToolReferenceBlock),
144    Raw(TypedObject),
145}
146
147type_tag_union_deserialize!(ToolResultContentBlock {
148    "text" => Text,
149    "image" => Image,
150    "search_result" => SearchResult,
151    "document" => Document,
152    "tool_reference" => ToolReference,
153});
154
155pub type McpToolResultContent = StringOrArray<TextBlock>;
156
157#[cfg(test)]
158mod tests {
159    use serde_json::json;
160
161    use super::*;
162
163    #[test]
164    fn tag_dispatch_falls_back_to_raw() {
165        // Unknown tag round-trips unchanged through Raw.
166        let wire = json!({"type": "hologram", "payload": {"x": 1}});
167        let block: ContentBlockParam = serde_json::from_value(wire.clone()).unwrap();
168        assert!(matches!(block, ContentBlockParam::Raw(_)));
169        assert_eq!(serde_json::to_value(&block).unwrap(), wire);
170
171        // A known tag whose body no longer parses degrades to Raw and is
172        // forwarded unchanged (pre-dispatch untagged behavior).
173        let wire = json!({"type": "tool_use", "id": 42});
174        let block: ContentBlockParam = serde_json::from_value(wire.clone()).unwrap();
175        assert!(matches!(block, ContentBlockParam::Raw(_)));
176        assert_eq!(serde_json::to_value(&block).unwrap(), wire);
177    }
178}