gproxy_protocol/transform/openai/compact/claude/
response.rs1use crate::claude::count_tokens::types::BetaServerToolUseName;
2use crate::claude::create_message::response::ClaudeCreateMessageResponse;
3use crate::claude::create_message::types as ct;
4use crate::openai::compact_response::response::OpenAiCompactResponse;
5use crate::openai::compact_response::response::{
6 OpenAiCompactedResponseObject, ResponseBody as CompactResponseBody,
7};
8use crate::openai::compact_response::types as cpt;
9use crate::openai::count_tokens::types as ot;
10use crate::openai::types::OpenAiResponseHeaders;
11use crate::transform::openai::model_list::claude::utils::openai_error_response_from_claude;
12use crate::transform::utils::TransformError;
13
14impl TryFrom<ClaudeCreateMessageResponse> for OpenAiCompactResponse {
15 type Error = TransformError;
16
17 fn try_from(value: ClaudeCreateMessageResponse) -> Result<Self, TransformError> {
18 Ok(match value {
19 ClaudeCreateMessageResponse::Success {
20 stats_code,
21 headers,
22 body,
23 } => {
24 let mut output = Vec::new();
25 let mut message_content = Vec::new();
26
27 for (index, block) in body.content.into_iter().enumerate() {
28 match block {
29 ct::BetaContentBlock::Text(text) => {
30 if !text.text.is_empty() {
31 message_content.push(
32 cpt::CompactedResponseMessageContent::OutputText(
33 ot::ResponseOutputText {
34 annotations: Vec::new(),
35 logprobs: None,
36 text: text.text,
37 type_: ot::ResponseOutputTextType::OutputText,
38 },
39 ),
40 );
41 }
42 }
43 ct::BetaContentBlock::Thinking(thinking) => {
44 if !thinking.thinking.is_empty() {
45 message_content.push(
46 cpt::CompactedResponseMessageContent::ReasoningText(
47 ot::ResponseReasoningTextContent {
48 text: thinking.thinking,
49 type_:
50 ot::ResponseReasoningTextContentType::ReasoningText,
51 },
52 ),
53 );
54 }
55 }
56 ct::BetaContentBlock::RedactedThinking(thinking) => {
57 if !thinking.data.is_empty() {
58 output.push(cpt::CompactedResponseOutputItem::CompactionItem(
59 ot::ResponseCompactionItemParam {
60 encrypted_content: thinking.data,
61 type_: ot::ResponseCompactionItemType::Compaction,
62 id: Some(format!("compaction_{index}")),
63 created_by: None,
64 },
65 ));
66 }
67 }
68 ct::BetaContentBlock::ToolUse(tool_use) => {
69 output.push(cpt::CompactedResponseOutputItem::FunctionToolCall(
70 ot::ResponseFunctionToolCall {
71 arguments: serde_json::to_string(&tool_use.input)
72 .unwrap_or_else(|_| "{}".to_string()),
73 call_id: tool_use.id.clone(),
74 name: tool_use.name,
75 type_: ot::ResponseFunctionToolCallType::FunctionCall,
76 id: Some(tool_use.id),
77 status: Some(ot::ResponseItemStatus::Completed),
78 },
79 ));
80 }
81 ct::BetaContentBlock::ServerToolUse(tool_use) => {
82 output.push(cpt::CompactedResponseOutputItem::FunctionToolCall(
83 ot::ResponseFunctionToolCall {
84 arguments: serde_json::to_string(&tool_use.input)
85 .unwrap_or_else(|_| "{}".to_string()),
86 call_id: tool_use.id.clone(),
87 name: match tool_use.name {
88 BetaServerToolUseName::WebSearch => "web_search",
89 BetaServerToolUseName::WebFetch => "web_fetch",
90 BetaServerToolUseName::CodeExecution => "code_execution",
91 BetaServerToolUseName::BashCodeExecution => {
92 "bash_code_execution"
93 }
94 BetaServerToolUseName::TextEditorCodeExecution => {
95 "text_editor_code_execution"
96 }
97 BetaServerToolUseName::ToolSearchToolRegex => {
98 "tool_search_tool_regex"
99 }
100 BetaServerToolUseName::ToolSearchToolBm25 => {
101 "tool_search_tool_bm25"
102 }
103 }
104 .to_string(),
105 type_: ot::ResponseFunctionToolCallType::FunctionCall,
106 id: Some(tool_use.id),
107 status: Some(ot::ResponseItemStatus::Completed),
108 },
109 ));
110 }
111 ct::BetaContentBlock::McpToolUse(tool_use) => {
112 output.push(cpt::CompactedResponseOutputItem::FunctionToolCall(
113 ot::ResponseFunctionToolCall {
114 arguments: serde_json::to_string(&tool_use.input)
115 .unwrap_or_else(|_| "{}".to_string()),
116 call_id: tool_use.id.clone(),
117 name: tool_use.name,
118 type_: ot::ResponseFunctionToolCallType::FunctionCall,
119 id: Some(tool_use.id),
120 status: Some(ot::ResponseItemStatus::Completed),
121 },
122 ));
123 }
124 ct::BetaContentBlock::Compaction(compaction) => {
125 output.push(cpt::CompactedResponseOutputItem::CompactionItem(
126 ot::ResponseCompactionItemParam {
127 encrypted_content: compaction.content.unwrap_or_default(),
128 type_: ot::ResponseCompactionItemType::Compaction,
129 id: Some(format!("compaction_{index}")),
130 created_by: None,
131 },
132 ));
133 }
134 other => {
135 message_content.push(cpt::CompactedResponseMessageContent::Text(
136 cpt::CompactedResponseTextContent {
137 text: format!("{other:?}"),
138 type_: cpt::CompactedResponseTextContentType::Text,
139 },
140 ));
141 }
142 }
143 }
144
145 if !message_content.is_empty() {
146 let status = match body.stop_reason {
147 Some(
148 ct::BetaStopReason::MaxTokens
149 | ct::BetaStopReason::Refusal
150 | ct::BetaStopReason::ModelContextWindowExceeded,
151 ) => ot::ResponseItemStatus::Incomplete,
152 _ => ot::ResponseItemStatus::Completed,
153 };
154 output.push(cpt::CompactedResponseOutputItem::Message(
155 cpt::CompactedResponseMessage {
156 id: format!("{}_message_0", body.id),
157 content: message_content,
158 role: cpt::CompactedResponseMessageRole::Assistant,
159 status,
160 type_: cpt::CompactedResponseMessageType::Message,
161 },
162 ));
163 }
164
165 OpenAiCompactResponse::Success {
166 stats_code,
167 headers: OpenAiResponseHeaders {
168 extra: headers.extra,
169 },
170 body: CompactResponseBody {
171 id: body.id,
172 created_at: 0,
173 object: OpenAiCompactedResponseObject::ResponseCompaction,
174 output,
175 usage: {
176 let input_tokens = body
177 .usage
178 .input_tokens
179 .saturating_add(body.usage.cache_creation_input_tokens)
180 .saturating_add(body.usage.cache_read_input_tokens);
181 cpt::ResponseUsage {
182 input_tokens,
183 input_tokens_details: cpt::ResponseInputTokensDetails {
184 cached_tokens: body.usage.cache_read_input_tokens,
185 },
186 output_tokens: body.usage.output_tokens,
187 output_tokens_details: cpt::ResponseOutputTokensDetails {
188 reasoning_tokens: 0,
189 },
190 total_tokens: input_tokens.saturating_add(body.usage.output_tokens),
191 }
192 },
193 },
194 }
195 }
196 ClaudeCreateMessageResponse::Error {
197 stats_code,
198 headers,
199 body,
200 } => OpenAiCompactResponse::Error {
201 stats_code,
202 headers: OpenAiResponseHeaders {
203 extra: headers.extra,
204 },
205 body: openai_error_response_from_claude(stats_code, body),
206 },
207 })
208 }
209}