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