diff --git a/src-tauri/src/proxy/providers/claude.rs b/src-tauri/src/proxy/providers/claude.rs index 6443f70a0..c8db6b919 100644 --- a/src-tauri/src/proxy/providers/claude.rs +++ b/src-tauri/src/proxy/providers/claude.rs @@ -225,78 +225,12 @@ pub fn normalize_anthropic_messages_for_provider( return false; } - let mut changed = normalize_anthropic_system_role_messages(body); - changed |= normalize_anthropic_tool_thinking_history_for_provider(body, provider, api_format); + let mut changed = + normalize_anthropic_tool_thinking_history_for_provider(body, provider, api_format); changed |= normalize_deepseek_thinking_disabled_strip_effort(body, provider); changed } -fn normalize_anthropic_system_role_messages(body: &mut Value) -> bool { - let mut system_parts = Vec::new(); - let changed = { - let Some(messages) = body.get_mut("messages").and_then(Value::as_array_mut) else { - return false; - }; - - let original_len = messages.len(); - let mut kept_messages = Vec::with_capacity(messages.len()); - for message in std::mem::take(messages) { - if message.get("role").and_then(Value::as_str) == Some("system") { - if let Some(content) = message.get("content") { - append_anthropic_system_parts(content, &mut system_parts); - } - } else { - kept_messages.push(message); - } - } - - let changed = kept_messages.len() != original_len; - *messages = kept_messages; - changed - }; - - if !changed || system_parts.is_empty() { - return changed; - } - - let mut merged_parts = Vec::new(); - if let Some(existing) = body.get("system") { - append_anthropic_system_parts(existing, &mut merged_parts); - } - merged_parts.extend(system_parts); - - if !merged_parts.is_empty() { - body["system"] = Value::Array(merged_parts); - } - - true -} - -fn append_anthropic_system_parts(content: &Value, parts: &mut Vec) { - match content { - Value::String(text) if !text.trim().is_empty() => { - parts.push(json!({ - "type": "text", - "text": text - })); - } - Value::Array(items) => { - for item in items { - append_anthropic_system_parts(item, parts); - } - } - Value::Object(obj) - if obj - .get("text") - .and_then(Value::as_str) - .is_some_and(|text| !text.trim().is_empty()) => - { - parts.push(Value::Object(obj.clone())); - } - _ => {} - } -} - fn normalize_anthropic_tool_thinking_history(body: &mut Value) -> bool { let Some(messages) = body.get_mut("messages").and_then(Value::as_array_mut) else { return false; @@ -2189,7 +2123,10 @@ mod tests { } #[test] - fn test_anthropic_system_role_messages_move_to_top_level_system() { + fn test_anthropic_messages_no_longer_hoists_system_role_messages() { + // After reverting #3775, role=system messages are left in `messages[]` + // (DeepSeek's endpoint accepts them natively) and the top-level `system` + // field is untouched, preserving the request prefix. let provider = create_provider(json!({ "env": { "ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic", @@ -2211,15 +2148,13 @@ mod tests { let changed = normalize_anthropic_messages_for_provider(&mut body, &provider, "anthropic"); - assert!(changed); + assert!(!changed); let messages = body["messages"].as_array().unwrap(); - assert_eq!(messages.len(), 1); - assert_eq!(messages[0]["role"], "user"); - - let system = body["system"].as_array().unwrap(); - assert_eq!(system[0]["text"], "Existing top-level system."); - assert_eq!(system[1]["text"], "Message system one."); - assert_eq!(system[2]["text"], "Message system two."); + assert_eq!(messages.len(), 3); + assert_eq!(messages[0]["role"], "system"); + assert_eq!(messages[1]["role"], "user"); + assert_eq!(messages[2]["role"], "system"); + assert_eq!(body["system"], "Existing top-level system."); } #[test]