gproxy_transform/transform/compact/claude_to_openai/
output.rs1use crate::protocol::{claude, openai};
2use crate::transform::TransformContext;
3
4use super::DEFAULT_REASONING_ID;
5use super::tools::{
6 compact_function_call_output_item, compact_server_tool_use_item,
7 response_mcp_tool_result_content_to_text, server_tool_result_output,
8};
9use super::util::{claude_usage_to_openai, json_object_to_string};
10
11pub fn response(
12 input: claude::CreateMessageResponseBody,
13 _: &TransformContext,
14) -> openai::CompactedResponseObject {
15 openai::CompactedResponseObject {
16 id: input.id.clone(),
17 created_at: 0,
18 object: openai::ResponseCompactionObjectType::ResponseCompaction,
19 output: claude_content_to_compact_output(input.id, input.content, &input.stop_reason),
20 usage: claude_usage_to_openai(input.usage),
21 extra: Default::default(),
22 }
23}
24
25fn claude_content_to_compact_output(
26 id: String,
27 content: Vec<claude::ContentBlock>,
28 stop_reason: &claude::StopReason,
29) -> Vec<openai::CompactResponseItem> {
30 let mut message_parts = Vec::new();
31 let mut output = Vec::new();
32
33 for block in content {
34 match block {
35 claude::ContentBlock::Text(block) => {
36 message_parts.push(openai::CompactMessageContentPart::Text(
37 openai::CompactTextContent {
38 text: block.text,
39 type_: openai::CompactTextContentType::Text,
40 extra: Default::default(),
41 },
42 ));
43 }
44 claude::ContentBlock::Thinking(block) => {
45 output.push(openai::CompactResponseItem::Typed(
46 openai::TypedResponseItem::Reasoning {
47 id: Some(block.signature),
48 summary: Vec::new(),
49 content: Some(vec![openai::ResponseReasoningTextPart {
50 text: block.thinking,
51 type_: openai::ResponseReasoningTextType::ReasoningText,
52 extra: Default::default(),
53 }]),
54 encrypted_content: None,
55 status: Some(openai::ResponseItemLifecycleStatus::Completed),
56 extra: Default::default(),
57 },
58 ));
59 }
60 claude::ContentBlock::RedactedThinking(block) => {
61 output.push(openai::CompactResponseItem::Typed(
62 openai::TypedResponseItem::Reasoning {
63 id: Some(DEFAULT_REASONING_ID.to_owned()),
64 summary: Vec::new(),
65 content: None,
66 encrypted_content: Some(block.data),
67 status: Some(openai::ResponseItemLifecycleStatus::Completed),
68 extra: Default::default(),
69 },
70 ));
71 }
72 claude::ContentBlock::ToolUse(block) => {
73 output.push(openai::CompactResponseItem::Typed(
74 openai::TypedResponseItem::FunctionCall {
75 arguments: json_object_to_string(&block.input),
76 call_id: block.id.clone(),
77 name: block.name,
78 id: Some(block.id),
79 caller: None,
80 namespace: None,
81 status: Some(openai::ResponseItemLifecycleStatus::Completed),
82 extra: Default::default(),
83 },
84 ));
85 }
86 claude::ContentBlock::ServerToolUse(block) => {
87 output.push(compact_server_tool_use_item(
88 block.id,
89 block.input,
90 block.name,
91 ));
92 }
93 claude::ContentBlock::WebSearchToolResult(block) => {
94 output.push(compact_function_call_output_item(
95 block.tool_use_id,
96 server_tool_result_output(&block.content),
97 ));
98 }
99 claude::ContentBlock::WebFetchToolResult(block) => {
100 output.push(compact_function_call_output_item(
101 block.tool_use_id,
102 server_tool_result_output(&block.content),
103 ));
104 }
105 claude::ContentBlock::AdvisorToolResult(block) => {
106 output.push(compact_function_call_output_item(
107 block.tool_use_id,
108 server_tool_result_output(&block.content),
109 ));
110 }
111 claude::ContentBlock::CodeExecutionToolResult(block) => {
112 output.push(compact_function_call_output_item(
113 block.tool_use_id,
114 server_tool_result_output(&block.content),
115 ));
116 }
117 claude::ContentBlock::BashCodeExecutionToolResult(block) => {
118 output.push(compact_function_call_output_item(
119 block.tool_use_id,
120 server_tool_result_output(&block.content),
121 ));
122 }
123 claude::ContentBlock::TextEditorCodeExecutionToolResult(block) => {
124 output.push(compact_function_call_output_item(
125 block.tool_use_id,
126 server_tool_result_output(&block.content),
127 ));
128 }
129 claude::ContentBlock::ToolSearchToolResult(block) => {
130 output.push(compact_function_call_output_item(
131 block.tool_use_id,
132 server_tool_result_output(&block.content),
133 ));
134 }
135 claude::ContentBlock::McpToolUse(block) => {
136 output.push(openai::CompactResponseItem::Typed(
137 openai::TypedResponseItem::McpCall {
138 id: block.id,
139 arguments: json_object_to_string(&block.input),
140 name: block.name,
141 server_label: block.server_name,
142 approval_request_id: None,
143 error: None,
144 output: None,
145 status: Some(openai::ResponseMcpCallStatus::Completed),
146 extra: Default::default(),
147 },
148 ));
149 }
150 claude::ContentBlock::McpToolResult(block) => {
151 output.push(compact_function_call_output_item(
152 block.tool_use_id,
153 openai::ResponseOutput::Text(response_mcp_tool_result_content_to_text(
154 block.content,
155 )),
156 ));
157 }
158 claude::ContentBlock::Compaction(block) => {
159 if let Some(text) = block.content {
160 message_parts.push(openai::CompactMessageContentPart::SummaryText(
161 openai::CompactSummaryTextContent {
162 text,
163 type_: openai::CompactSummaryTextContentType::SummaryText,
164 extra: Default::default(),
165 },
166 ));
167 }
168 output.push(openai::CompactResponseItem::Typed(
169 openai::TypedResponseItem::Compaction {
170 encrypted_content: block.encrypted_content,
171 id: None,
172 created_by: None,
173 extra: Default::default(),
174 },
175 ));
176 }
177 _ => {}
178 }
179 }
180
181 if !message_parts.is_empty() {
182 output.insert(
183 0,
184 openai::CompactResponseItem::Message(openai::CompactMessageItem {
185 id,
186 type_: openai::ResponseMessageItemType::Message,
187 content: message_parts,
188 role: openai::CompactMessageRole::Assistant,
189 status: compact_message_status(stop_reason),
190 phase: None,
191 extra: Default::default(),
192 }),
193 );
194 }
195
196 output
197}
198
199fn compact_message_status(stop_reason: &claude::StopReason) -> openai::ResponseItemLifecycleStatus {
200 match stop_reason {
201 claude::StopReason::Known(claude::StopReasonKnown::MaxTokens)
202 | claude::StopReason::Known(claude::StopReasonKnown::Refusal)
203 | claude::StopReason::Known(claude::StopReasonKnown::ModelContextWindowExceeded) => {
204 openai::ResponseItemLifecycleStatus::Incomplete
205 }
206 _ => openai::ResponseItemLifecycleStatus::Completed,
207 }
208}