Skip to main content

gproxy_transform/transform/compact/claude_to_openai/
request.rs

1use 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}