gproxy_protocol/transform/claude/generate_content/openai_chat_completions/
response.rs1use 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}