Skip to main content

gproxy_protocol/transform/openai/compact/claude/
response.rs

1use 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}