Skip to main content

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

1use crate::claude::count_tokens::types as ct;
2use crate::claude::create_message::request::{
3    ClaudeCreateMessageRequest, PathParameters, QueryParameters, RequestBody, RequestHeaders,
4};
5use crate::claude::create_message::types::BetaSystemPrompt;
6use crate::openai::compact_response::request::OpenAiCompactRequest;
7use crate::openai::count_tokens::types as ot;
8use crate::transform::openai::compact::utils::COMPACT_MAX_OUTPUT_TOKENS;
9use crate::transform::openai::compact::utils::claude_compact_system_instruction;
10use crate::transform::openai::count_tokens::claude::utils::{
11    ClaudeToolUseIdMapper, openai_message_content_to_claude, openai_role_to_claude,
12    push_message_block,
13};
14use crate::transform::openai::count_tokens::utils::{
15    openai_function_call_output_content_to_text, openai_input_to_items,
16    openai_reasoning_summary_to_text,
17};
18use crate::transform::utils::TransformError;
19
20impl TryFrom<OpenAiCompactRequest> for ClaudeCreateMessageRequest {
21    type Error = TransformError;
22
23    fn try_from(value: OpenAiCompactRequest) -> Result<Self, TransformError> {
24        let body = value.body;
25        let mut messages = Vec::new();
26        let mut tool_use_ids = ClaudeToolUseIdMapper::default();
27
28        for item in openai_input_to_items(body.input) {
29            match item {
30                ot::ResponseInputItem::Message(message) => {
31                    messages.push(ct::BetaMessageParam {
32                        content: openai_message_content_to_claude(message.content),
33                        role: openai_role_to_claude(message.role),
34                    });
35                }
36                ot::ResponseInputItem::OutputMessage(message) => {
37                    let text = message
38                        .content
39                        .into_iter()
40                        .map(|part| match part {
41                            ot::ResponseOutputContent::Text(text) => text.text,
42                            ot::ResponseOutputContent::Refusal(refusal) => refusal.refusal,
43                        })
44                        .filter(|text| !text.is_empty())
45                        .collect::<Vec<_>>()
46                        .join("\n");
47                    if !text.is_empty() {
48                        messages.push(ct::BetaMessageParam {
49                            content: ct::BetaMessageContent::Text(text),
50                            role: ct::BetaMessageRole::Assistant,
51                        });
52                    }
53                }
54                ot::ResponseInputItem::FunctionToolCall(tool_call) => {
55                    let tool_use_id = tool_use_ids.tool_use_id(tool_call.call_id);
56                    let input = serde_json::from_str::<ct::JsonObject>(&tool_call.arguments)
57                        .unwrap_or_default();
58                    push_message_block(
59                        &mut messages,
60                        ct::BetaMessageRole::Assistant,
61                        ct::BetaContentBlockParam::ToolUse(ct::BetaToolUseBlockParam {
62                            id: tool_use_id,
63                            input,
64                            name: tool_call.name,
65                            type_: ct::BetaToolUseBlockType::ToolUse,
66                            cache_control: None,
67                            caller: None,
68                        }),
69                    );
70                }
71                ot::ResponseInputItem::FunctionCallOutput(tool_result) => {
72                    let tool_use_id = tool_use_ids.tool_use_id(tool_result.call_id);
73                    let output_text =
74                        openai_function_call_output_content_to_text(&tool_result.output);
75                    push_message_block(
76                        &mut messages,
77                        ct::BetaMessageRole::User,
78                        ct::BetaContentBlockParam::ToolResult(ct::BetaToolResultBlockParam {
79                            tool_use_id,
80                            type_: ct::BetaToolResultBlockType::ToolResult,
81                            cache_control: None,
82                            content: if output_text.is_empty() {
83                                None
84                            } else {
85                                Some(ct::BetaToolResultBlockParamContent::Text(output_text))
86                            },
87                            is_error: None,
88                        }),
89                    );
90                }
91                ot::ResponseInputItem::ReasoningItem(reasoning) => {
92                    let mut thinking = openai_reasoning_summary_to_text(&reasoning.summary);
93                    if thinking.is_empty()
94                        && let Some(encrypted) = reasoning.encrypted_content
95                    {
96                        thinking = encrypted;
97                    }
98                    if !thinking.is_empty()
99                        && let Some(signature) = reasoning.id.filter(|id| !id.is_empty())
100                    {
101                        messages.push(ct::BetaMessageParam {
102                            content: ct::BetaMessageContent::Blocks(vec![
103                                ct::BetaContentBlockParam::Thinking(ct::BetaThinkingBlockParam {
104                                    signature,
105                                    thinking,
106                                    type_: ct::BetaThinkingBlockType::Thinking,
107                                }),
108                            ]),
109                            role: ct::BetaMessageRole::Assistant,
110                        });
111                    }
112                }
113                other => {
114                    messages.push(ct::BetaMessageParam {
115                        content: ct::BetaMessageContent::Text(format!("{other:?}")),
116                        role: ct::BetaMessageRole::User,
117                    });
118                }
119            }
120        }
121
122        Ok(ClaudeCreateMessageRequest {
123            method: ct::HttpMethod::Post,
124            path: PathParameters::default(),
125            query: QueryParameters::default(),
126            headers: RequestHeaders::default(),
127            body: RequestBody {
128                max_tokens: COMPACT_MAX_OUTPUT_TOKENS,
129                messages,
130                model: ct::Model::Custom(body.model),
131                container: None,
132                context_management: None,
133                inference_geo: None,
134                mcp_servers: None,
135                metadata: None,
136                cache_control: None,
137                output_config: None,
138                service_tier: None,
139                speed: None,
140                stop_sequences: None,
141                stream: None,
142                system: Some(BetaSystemPrompt::Text(claude_compact_system_instruction(
143                    body.instructions,
144                ))),
145                temperature: None,
146                thinking: None,
147                tool_choice: None,
148                tools: None,
149                top_k: None,
150                top_p: None,
151            },
152        })
153    }
154}