mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-09 04:30:20 +08:00
Merge pull request #650 from stabey/pr/claude-system-responses-20260620
fix(ai-formats): preserve Claude in-message system guidance in Responses
This commit is contained in:
@@ -829,6 +829,56 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_request_to_responses_preserves_in_message_system_guidance_as_developer_item() {
|
||||
let body = json!({
|
||||
"model": "claude-sonnet",
|
||||
"system": [{
|
||||
"type": "text",
|
||||
"text": "Be exact."
|
||||
}],
|
||||
"messages": [
|
||||
{"role": "user", "content": "hello"},
|
||||
{
|
||||
"role": "system",
|
||||
"content": "x-anthropic-billing-header: internal-billing-marker\nSessionStart hook additional context: follow the house style."
|
||||
},
|
||||
{"role": "assistant", "content": "visible answer"},
|
||||
{"role": "user", "content": "continue"}
|
||||
],
|
||||
"max_tokens": 128
|
||||
});
|
||||
|
||||
let converted = registry::convert_request(
|
||||
"claude:messages",
|
||||
"openai:responses",
|
||||
&body,
|
||||
&FormatContext::default().with_mapped_model("gpt-5.1"),
|
||||
)
|
||||
.expect("responses request");
|
||||
|
||||
let input = converted["input"].as_array().expect("responses input");
|
||||
assert_eq!(input.len(), 5);
|
||||
assert_eq!(input[0]["role"], "developer");
|
||||
assert_eq!(input[0]["content"][0]["text"], "Be exact.");
|
||||
assert_eq!(input[1]["role"], "user");
|
||||
assert_eq!(input[1]["content"][0]["text"], "hello");
|
||||
assert_eq!(input[2]["role"], "developer");
|
||||
assert_eq!(
|
||||
input[2]["content"][0]["text"],
|
||||
"SessionStart hook additional context: follow the house style."
|
||||
);
|
||||
assert!(!input[2]["content"][0]["text"]
|
||||
.as_str()
|
||||
.expect("developer guidance text")
|
||||
.contains("x-anthropic-billing-header:"));
|
||||
assert_eq!(input[3]["role"], "assistant");
|
||||
assert_eq!(input[3]["content"][0]["text"], "visible answer");
|
||||
assert_eq!(input[4]["role"], "user");
|
||||
assert_eq!(input[4]["content"][0]["text"], "continue");
|
||||
assert!(converted.get("instructions").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openai_responses_request_normalizer_strips_content_cache_control() {
|
||||
let body = json!({
|
||||
|
||||
@@ -16,9 +16,9 @@ use crate::{
|
||||
openai_response_format_to_canonical, openai_responses_extension,
|
||||
openai_responses_generation_config, openai_responses_input_to_canonical_messages,
|
||||
openai_responses_tool_choice_to_canonical, openai_responses_tools_to_canonical,
|
||||
openai_tool_choice_raw_to_responses, CanonicalContentBlock, CanonicalInstruction,
|
||||
CanonicalRequest, CanonicalRole, CanonicalThinkingConfig, CanonicalToolChoice,
|
||||
CanonicalToolDefinition, OPENAI_RESPONSES_EXTENSION_NAMESPACE,
|
||||
openai_tool_choice_raw_to_responses, strip_claude_billing_header, CanonicalContentBlock,
|
||||
CanonicalInstruction, CanonicalRequest, CanonicalRole, CanonicalThinkingConfig,
|
||||
CanonicalToolChoice, CanonicalToolDefinition, OPENAI_RESPONSES_EXTENSION_NAMESPACE,
|
||||
OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
||||
},
|
||||
};
|
||||
@@ -305,6 +305,12 @@ fn canonical_messages_to_responses_input(canonical: &CanonicalRequest) -> Option
|
||||
let mut next_generated_tool_call_index = 0usize;
|
||||
let mut pending_tool_call_ids = VecDeque::new();
|
||||
for message in &canonical.messages {
|
||||
let strip_claude_billing_header_from_text =
|
||||
is_claude_messages_request(&canonical.extensions)
|
||||
&& matches!(
|
||||
message.role,
|
||||
CanonicalRole::System | CanonicalRole::Developer
|
||||
);
|
||||
let role = match message.role {
|
||||
CanonicalRole::Assistant => "assistant",
|
||||
CanonicalRole::Tool | CanonicalRole::User | CanonicalRole::Unknown => "user",
|
||||
@@ -314,6 +320,11 @@ fn canonical_messages_to_responses_input(canonical: &CanonicalRequest) -> Option
|
||||
CanonicalRole::Developer if is_openai_responses_input_message(&message.extensions) => {
|
||||
"developer"
|
||||
}
|
||||
CanonicalRole::System | CanonicalRole::Developer
|
||||
if is_claude_messages_request(&canonical.extensions) =>
|
||||
{
|
||||
"developer"
|
||||
}
|
||||
CanonicalRole::System | CanonicalRole::Developer => continue,
|
||||
};
|
||||
let mut content = Vec::new();
|
||||
@@ -396,7 +407,11 @@ fn canonical_messages_to_responses_input(canonical: &CanonicalRequest) -> Option
|
||||
}
|
||||
}
|
||||
other => {
|
||||
if let Some(part) = canonical_block_to_responses_input_part(other, role) {
|
||||
if let Some(part) = canonical_block_to_responses_input_part(
|
||||
other,
|
||||
role,
|
||||
strip_claude_billing_header_from_text,
|
||||
) {
|
||||
content.push(part);
|
||||
}
|
||||
}
|
||||
@@ -592,9 +607,15 @@ fn is_openai_responses_reasoning_history_block(extensions: &BTreeMap<String, Val
|
||||
fn canonical_block_to_responses_input_part(
|
||||
block: &CanonicalContentBlock,
|
||||
role: &str,
|
||||
strip_claude_billing_header_from_text: bool,
|
||||
) -> Option<Value> {
|
||||
match block {
|
||||
CanonicalContentBlock::Text { text, .. } => {
|
||||
let text = if strip_claude_billing_header_from_text {
|
||||
strip_claude_billing_header(text)
|
||||
} else {
|
||||
text.clone()
|
||||
};
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user