Skip to main content

gproxy_protocol/transform/claude/generate_content/openai_chat_completions/
response.rs

1use crate::claude::count_tokens::types::BetaToolUseBlockType;
2use crate::claude::create_message::response::ClaudeCreateMessageResponse;
3use crate::claude::create_message::types::{
4    BetaContentBlock, BetaMessage, BetaMessageRole, BetaMessageType, BetaServiceTier,
5    BetaStopReason, BetaTextBlock, BetaTextBlockType, Model,
6};
7use crate::claude::types::ClaudeResponseHeaders;
8use crate::openai::create_chat_completions::response::OpenAiChatCompletionsResponse;
9use crate::openai::create_chat_completions::types::{
10    ChatCompletionFinishReason, ChatCompletionMessageToolCall, ChatCompletionServiceTier,
11};
12use crate::transform::claude::generate_content::utils::{
13    beta_usage_from_counts, parse_json_object_or_empty,
14};
15use crate::transform::claude::utils::beta_error_response_from_status_message;
16use crate::transform::utils::TransformError;
17
18impl TryFrom<OpenAiChatCompletionsResponse> for ClaudeCreateMessageResponse {
19    type Error = TransformError;
20
21    fn try_from(value: OpenAiChatCompletionsResponse) -> Result<Self, TransformError> {
22        Ok(match value {
23            OpenAiChatCompletionsResponse::Success {
24                stats_code,
25                headers,
26                body,
27            } => {
28                let choice = body.choices.into_iter().next();
29                let mut content = Vec::new();
30                let mut has_tool_use = false;
31                let mut has_refusal = false;
32                let finish_reason = choice.as_ref().map(|choice| choice.finish_reason.clone());
33
34                if let Some(choice) = choice {
35                    if let Some(text) = choice.message.content {
36                        content.push(BetaContentBlock::Text(BetaTextBlock {
37                            citations: None,
38                            text,
39                            type_: BetaTextBlockType::Text,
40                        }));
41                    }
42                    if let Some(refusal) = choice.message.refusal {
43                        has_refusal = true;
44                        content.push(BetaContentBlock::Text(BetaTextBlock {
45                            citations: None,
46                            text: refusal,
47                            type_: BetaTextBlockType::Text,
48                        }));
49                    }
50                    if let Some(function_call) = choice.message.function_call {
51                        has_tool_use = true;
52                        content.push(BetaContentBlock::ToolUse(
53                            crate::claude::create_message::types::BetaToolUseBlock {
54                                id: "function_call".to_string(),
55                                input: parse_json_object_or_empty(&function_call.arguments),
56                                name: function_call.name,
57                                type_: BetaToolUseBlockType::ToolUse,
58                                cache_control: None,
59                                caller: None,
60                            },
61                        ));
62                    }
63                    if let Some(tool_calls) = choice.message.tool_calls {
64                        for call in tool_calls {
65                            match call {
66                                ChatCompletionMessageToolCall::Function(call) => {
67                                    has_tool_use = true;
68                                    content.push(BetaContentBlock::ToolUse(
69                                        crate::claude::create_message::types::BetaToolUseBlock {
70                                            id: call.id,
71                                            input: parse_json_object_or_empty(
72                                                &call.function.arguments,
73                                            ),
74                                            name: call.function.name,
75                                            type_: BetaToolUseBlockType::ToolUse,
76                                            cache_control: None,
77                                            caller: None,
78                                        },
79                                    ));
80                                }
81                                ChatCompletionMessageToolCall::Custom(call) => {
82                                    has_tool_use = true;
83                                    content.push(BetaContentBlock::ToolUse(
84                                        crate::claude::create_message::types::BetaToolUseBlock {
85                                            id: call.id,
86                                            input: parse_json_object_or_empty(&call.custom.input),
87                                            name: call.custom.name,
88                                            type_: BetaToolUseBlockType::ToolUse,
89                                            cache_control: None,
90                                            caller: None,
91                                        },
92                                    ));
93                                }
94                            }
95                        }
96                    }
97                }
98
99                if content.is_empty() {
100                    content.push(BetaContentBlock::Text(BetaTextBlock {
101                        citations: None,
102                        text: String::new(),
103                        type_: BetaTextBlockType::Text,
104                    }));
105                }
106
107                let stop_reason = match finish_reason {
108                    Some(ChatCompletionFinishReason::Stop) => Some(BetaStopReason::EndTurn),
109                    Some(ChatCompletionFinishReason::Length) => Some(BetaStopReason::MaxTokens),
110                    Some(ChatCompletionFinishReason::ToolCalls)
111                    | Some(ChatCompletionFinishReason::FunctionCall) => {
112                        Some(BetaStopReason::ToolUse)
113                    }
114                    Some(ChatCompletionFinishReason::ContentFilter) => {
115                        Some(BetaStopReason::Refusal)
116                    }
117                    _ => {
118                        if has_tool_use {
119                            Some(BetaStopReason::ToolUse)
120                        } else if has_refusal {
121                            Some(BetaStopReason::Refusal)
122                        } else {
123                            Some(BetaStopReason::EndTurn)
124                        }
125                    }
126                };
127
128                let (input_tokens, cached_tokens, output_tokens) = body
129                    .usage
130                    .as_ref()
131                    .map(|usage| {
132                        let cached_tokens = usage
133                            .prompt_tokens_details
134                            .as_ref()
135                            .and_then(|details| details.cached_tokens)
136                            .unwrap_or(0);
137                        let total_input_tokens = if usage.total_tokens >= usage.completion_tokens {
138                            usage.total_tokens.saturating_sub(usage.completion_tokens)
139                        } else {
140                            usage.prompt_tokens
141                        };
142                        (
143                            total_input_tokens.saturating_sub(cached_tokens),
144                            cached_tokens,
145                            usage.completion_tokens,
146                        )
147                    })
148                    .unwrap_or((0, 0, 0));
149                let service_tier = match body.service_tier {
150                    Some(ChatCompletionServiceTier::Priority) => BetaServiceTier::Priority,
151                    _ => BetaServiceTier::Standard,
152                };
153                let usage = beta_usage_from_counts(
154                    input_tokens,
155                    cached_tokens,
156                    output_tokens,
157                    service_tier,
158                );
159
160                ClaudeCreateMessageResponse::Success {
161                    stats_code,
162                    headers: ClaudeResponseHeaders {
163                        extra: headers.extra,
164                    },
165                    body: BetaMessage {
166                        id: body.id,
167                        container: None,
168                        content,
169                        context_management: None,
170                        model: Model::Custom(body.model),
171                        role: BetaMessageRole::Assistant,
172                        stop_reason,
173                        stop_sequence: None,
174                        type_: BetaMessageType::Message,
175                        usage,
176                    },
177                }
178            }
179            OpenAiChatCompletionsResponse::Error {
180                stats_code,
181                headers,
182                body,
183            } => ClaudeCreateMessageResponse::Error {
184                stats_code,
185                headers: ClaudeResponseHeaders {
186                    extra: headers.extra,
187                },
188                body: beta_error_response_from_status_message(stats_code, body.error.message),
189            },
190        })
191    }
192}