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