gproxy_protocol/adapt/generate/
claude_compat.rs1use crate::{Dialect, HttpBody, Operation, OperationKey, WireRequest, connection::Bytes};
6use serde_json::Value;
7
8pub fn applies_to(operation: OperationKey) -> bool {
9 operation.dialect == Dialect::Claude
10 && matches!(
11 operation.operation,
12 Operation::GenerateContent | Operation::StreamGenerateContent | Operation::CountTokens
13 )
14}
15
16pub fn prepare(operation: OperationKey, request: &mut WireRequest<HttpBody>) {
19 if !applies_to(operation) {
20 return;
21 }
22 let HttpBody::Bytes(bytes) = &request.body else {
23 return;
24 };
25 let Ok(mut body) = serde_json::from_slice::<Value>(bytes) else {
26 return;
27 };
28 let Some(root) = body.as_object_mut() else {
29 return;
30 };
31 if root
32 .get("fallback_credit_token")
33 .is_some_and(|value| !value.is_null())
34 {
35 return;
36 }
37 let mut changed = false;
38 for field in ["temperature", "top_p", "top_k"] {
39 changed |= root.remove(field).is_some();
40 }
41 if let Some(messages) = root.get_mut("messages").and_then(Value::as_array_mut) {
42 let mut output = Vec::with_capacity(messages.len());
43 for mut message in messages.drain(..) {
44 let role = message.get("role").and_then(Value::as_str);
45 let user = role == Some("user");
46 let assistant = role == Some("assistant");
47 if let Some(blocks) = message.get_mut("content").and_then(Value::as_array_mut) {
48 if user {
49 for block in blocks.iter_mut() {
50 changed |= user_block(block);
51 }
52 }
53 if assistant && blocks.iter().any(is_document) {
54 let blocks = std::mem::take(blocks);
55 for mut block in blocks {
56 let mut part = message.clone();
57 if is_document(&block) {
58 user_block(&mut block);
59 part["role"] = Value::String("user".into());
60 }
61 part["content"] = Value::Array(vec![block]);
62 output.push(part);
63 }
64 changed = true;
65 continue;
66 }
67 }
68 output.push(message);
69 }
70 *messages = output;
71 }
72 if changed && let Ok(bytes) = serde_json::to_vec(&body) {
73 request.headers.remove(http::header::CONTENT_LENGTH);
74 request.body = HttpBody::Bytes(Bytes::from(bytes));
75 }
76}
77
78fn is_document(block: &Value) -> bool {
79 block.get("type").and_then(Value::as_str) == Some("document")
80}
81
82fn user_block(block: &mut Value) -> bool {
83 match block.get("type").and_then(Value::as_str) {
84 Some("text") => {
85 if block
86 .get("citations")
87 .and_then(Value::as_array)
88 .is_some_and(|values| !values.is_empty())
89 {
90 block
91 .as_object_mut()
92 .expect("text block is an object")
93 .remove("citations");
94 true
95 } else {
96 false
97 }
98 }
99 Some("search_result" | "tool_result" | "mcp_tool_result") => {
100 let mut changed = false;
101 if let Some(blocks) = block.get_mut("content").and_then(Value::as_array_mut) {
102 for block in blocks {
103 changed |= user_block(block);
104 }
105 }
106 changed
107 }
108 Some("document") => {
109 let mut changed = false;
110 if block.pointer("/source/type").and_then(Value::as_str) == Some("content")
111 && let Some(blocks) = block
112 .pointer_mut("/source/content")
113 .and_then(Value::as_array_mut)
114 {
115 for block in blocks {
116 changed |= user_block(block);
117 }
118 }
119 changed
120 }
121 Some("web_fetch_tool_result" | "web_fetch_result") => {
122 block.get_mut("content").is_some_and(user_block)
123 }
124 _ => false,
125 }
126}