gproxy_transform/transform/compact/claude_to_openai/
request.rs1use crate::protocol::{claude, openai};
2use crate::transform::generate_content::common::cache::{
3 claude_prompt_cache_key, openai_options_for_claude_root,
4};
5use crate::transform::{TransformContext, TransformError};
6
7use super::input::{claude_messages_to_openai_items, system_to_openai_item};
8use super::util::{
9 claude_previous_message_id_to_openai, claude_service_tier_to_compact, claude_system_to_text,
10 model_to_string,
11};
12
13pub fn request(
14 input: claude::CreateMessageRequestBody,
15 _: &TransformContext,
16) -> Result<openai::CompactResponseRequestBody, TransformError> {
17 let prompt_cache_key = claude_prompt_cache_key(&input);
18 let compact_instructions = compact_instructions(input.context_management.as_ref());
19 let system = input.system.and_then(claude_system_to_text);
20 let mut input_items = claude_messages_to_openai_items(input.messages);
21 if compact_instructions.is_some()
22 && let Some(system) = system.as_ref()
23 {
24 input_items.insert(0, system_to_openai_item(system.clone()));
25 }
26
27 Ok(openai::CompactResponseRequestBody {
28 input: Some(openai::ResponseInput::Items(input_items)),
29 instructions: compact_instructions.or(system),
30 model: openai::OpenAiModelId::Unknown(model_to_string(&input.model)),
31 previous_response_id: claude_previous_message_id_to_openai(input.diagnostics),
32 prompt_cache_key: Some(prompt_cache_key),
33 prompt_cache_options: Some(openai_options_for_claude_root(input.cache_control)),
34 prompt_cache_retention: None,
35 service_tier: claude_service_tier_to_compact(input.service_tier),
36 extra: Default::default(),
37 })
38}
39
40fn compact_instructions(
41 context_management: Option<&claude::ContextManagementConfig>,
42) -> Option<String> {
43 context_management
44 .and_then(|context| context.edits.as_ref())
45 .and_then(|edits| {
46 edits.iter().find_map(|edit| match edit {
47 claude::ContextEdit::Known(claude::KnownContextEdit::Compact {
48 instructions,
49 ..
50 }) => instructions.clone(),
51 _ => None,
52 })
53 })
54}
55
56#[cfg(test)]
57mod tests {
58 use serde_json::json;
59
60 use super::*;
61 use crate::protocol::{Operation, OperationKey, Provider};
62
63 #[test]
64 fn compact_reuses_claude_session_cache_key_and_root_policy() {
65 let input = serde_json::from_value(json!({
66 "model": "claude-sonnet-4-6",
67 "max_tokens": 32,
68 "cache_control": {"type": "ephemeral"},
69 "metadata": {
70 "user_id": "{\"session_id\":\"session-compact\"}"
71 },
72 "messages": [{"role": "user", "content": "compact this"}]
73 }))
74 .unwrap();
75 let ctx = TransformContext::new(
76 OperationKey::provider(Operation::CompactContent, Provider::Claude),
77 OperationKey::provider(Operation::CompactContent, Provider::OpenAi),
78 );
79
80 let output = request(input, &ctx).unwrap();
81
82 assert_eq!(output.prompt_cache_key.as_deref(), Some("session-compact"));
83 assert_eq!(
84 output
85 .prompt_cache_options
86 .as_ref()
87 .and_then(|options| options.mode.as_ref()),
88 Some(&openai::PromptCacheMode::Implicit)
89 );
90 }
91}