gproxy_protocol/transform/openai/compact/claude/
request.rs1use 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}