mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 16:37:46 +08:00
Merge origin/main into dev
This commit is contained in:
@@ -165,6 +165,7 @@ mod tests {
|
||||
convert_openai_chat_request_to_claude_request,
|
||||
convert_openai_chat_request_to_openai_responses_request,
|
||||
normalize_claude_request_to_openai_chat_request,
|
||||
normalize_gemini_request_to_openai_chat_request,
|
||||
normalize_openai_responses_request_to_openai_chat_request,
|
||||
};
|
||||
|
||||
@@ -219,6 +220,43 @@ mod tests {
|
||||
assert_eq!(converted["messages"][0]["content"], "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_request_to_chat_clamps_max_reasoning_effort_to_high() {
|
||||
let body = json!({
|
||||
"model": "claude-sonnet",
|
||||
"messages": [{"role": "user", "content": "hello"}],
|
||||
"thinking": {"type": "enabled", "budget_tokens": 1024},
|
||||
"output_config": {"effort": "max"},
|
||||
"max_tokens": 128,
|
||||
});
|
||||
|
||||
let converted =
|
||||
normalize_claude_request_to_openai_chat_request(&body).expect("openai chat request");
|
||||
|
||||
assert_eq!(converted["reasoning_effort"], "high");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_request_to_chat_clamps_xhigh_reasoning_effort_to_high() {
|
||||
let body = json!({
|
||||
"contents": [{
|
||||
"role": "user",
|
||||
"parts": [{"text": "hello"}]
|
||||
}],
|
||||
"generationConfig": {
|
||||
"thinkingConfig": {"thinkingBudget": 8192}
|
||||
}
|
||||
});
|
||||
|
||||
let converted = normalize_gemini_request_to_openai_chat_request(
|
||||
&body,
|
||||
"/v1beta/models/gemini-2.5-pro:generateContent",
|
||||
)
|
||||
.expect("openai chat request");
|
||||
|
||||
assert_eq!(converted["reasoning_effort"], "high");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn responses_request_normalizer_keeps_tool_history_chat_safe() {
|
||||
let call_id_one = "call_weather_123";
|
||||
@@ -333,6 +371,34 @@ mod tests {
|
||||
assert_eq!(messages[0]["content"], "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn responses_request_normalizer_clamps_chat_reasoning_effort_and_filters_extensions() {
|
||||
let body = json!({
|
||||
"model": "gpt-5.1",
|
||||
"input": "hello",
|
||||
"reasoning": {"effort": "xhigh"},
|
||||
"text": {"verbosity": "high"},
|
||||
"include": ["reasoning.encrypted_content"],
|
||||
"store": false,
|
||||
"service_tier": "priority",
|
||||
"prompt_cache_key": "cache_123",
|
||||
"safety_identifier": "user_123"
|
||||
});
|
||||
|
||||
let converted = normalize_openai_responses_request_to_openai_chat_request(&body)
|
||||
.expect("openai chat request");
|
||||
|
||||
assert_eq!(converted["reasoning_effort"], "high");
|
||||
assert_eq!(converted["verbosity"], "high");
|
||||
assert_eq!(converted["service_tier"], "priority");
|
||||
assert_eq!(converted["prompt_cache_key"], "cache_123");
|
||||
assert_eq!(converted["safety_identifier"], "user_123");
|
||||
assert!(converted.get("include").is_none());
|
||||
assert!(converted.get("store").is_none());
|
||||
assert!(converted.get("text").is_none());
|
||||
assert!(converted.get("reasoning").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_normalizer_preserves_multiple_claude_tool_results() {
|
||||
let body = json!({
|
||||
|
||||
@@ -193,6 +193,7 @@ pub fn to_raw(canonical: &CanonicalRequest) -> Value {
|
||||
.and_then(|value| value.get("effort"))
|
||||
.and_then(Value::as_str)
|
||||
})
|
||||
.and_then(openai_chat_reasoning_effort)
|
||||
{
|
||||
output.insert(
|
||||
"reasoning_effort".to_string(),
|
||||
@@ -205,12 +206,12 @@ pub fn to_raw(canonical: &CanonicalRequest) -> Value {
|
||||
"openai",
|
||||
&output,
|
||||
));
|
||||
output.extend(chat_compatible_responses_extension_object(
|
||||
output.extend(chat_compatible_openai_responses_extension_object(
|
||||
&canonical.extensions,
|
||||
OPENAI_RESPONSES_EXTENSION_NAMESPACE,
|
||||
&output,
|
||||
));
|
||||
output.extend(chat_compatible_responses_extension_object(
|
||||
output.extend(chat_compatible_openai_responses_extension_object(
|
||||
&canonical.extensions,
|
||||
OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
|
||||
&output,
|
||||
@@ -218,34 +219,29 @@ pub fn to_raw(canonical: &CanonicalRequest) -> Value {
|
||||
Value::Object(output)
|
||||
}
|
||||
|
||||
fn chat_compatible_responses_extension_object(
|
||||
fn openai_chat_reasoning_effort(value: &str) -> Option<&'static str> {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"low" => Some("low"),
|
||||
"medium" => Some("medium"),
|
||||
"high" | "xhigh" | "max" => Some("high"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn chat_compatible_openai_responses_extension_object(
|
||||
extensions: &std::collections::BTreeMap<String, Value>,
|
||||
namespace: &str,
|
||||
existing: &Map<String, Value>,
|
||||
) -> Map<String, Value> {
|
||||
const CHAT_COMPATIBLE_RESPONSES_FIELDS: &[&str] = &[
|
||||
"stream",
|
||||
"stream_options",
|
||||
"verbosity",
|
||||
"store",
|
||||
"service_tier",
|
||||
"safety_identifier",
|
||||
"prompt_cache_key",
|
||||
];
|
||||
extensions
|
||||
.get(namespace)
|
||||
.and_then(Value::as_object)
|
||||
.map(|object| {
|
||||
object
|
||||
.iter()
|
||||
.filter(|(key, _)| {
|
||||
CHAT_COMPATIBLE_RESPONSES_FIELDS.contains(&key.as_str())
|
||||
&& !existing.contains_key(*key)
|
||||
})
|
||||
.map(|(key, value)| (key.clone(), value.clone()))
|
||||
.collect()
|
||||
namespace_extension_object(extensions, namespace, existing)
|
||||
.into_iter()
|
||||
.filter(|(key, _)| {
|
||||
matches!(
|
||||
key.as_str(),
|
||||
"verbosity" | "service_tier" | "prompt_cache_key" | "safety_identifier" | "user"
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn force_stream_options(body: &mut Value, upstream_is_stream: bool) {
|
||||
|
||||
@@ -398,6 +398,23 @@ fn collect_codex_prompt_cache_control_anchors(value: &Value, anchors: &mut Vec<V
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_codex_cache_control_fields(value: &mut Value) {
|
||||
match value {
|
||||
Value::Object(object) => {
|
||||
object.remove("cache_control");
|
||||
for child in object.values_mut() {
|
||||
strip_codex_cache_control_fields(child);
|
||||
}
|
||||
}
|
||||
Value::Array(items) => {
|
||||
for child in items {
|
||||
strip_codex_cache_control_fields(child);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_codex_prompt_cache_control_seed(provider_request_body: &Value) -> Option<String> {
|
||||
let mut anchors = Vec::new();
|
||||
collect_codex_prompt_cache_control_anchors(provider_request_body, &mut anchors);
|
||||
@@ -780,6 +797,7 @@ pub fn apply_codex_openai_responses_special_body_edits(
|
||||
inject_codex_default_variation_prompt(body_object);
|
||||
}
|
||||
|
||||
strip_codex_cache_control_fields(provider_request_body);
|
||||
insert_codex_prompt_cache_key(provider_request_body, prompt_cache_key);
|
||||
}
|
||||
|
||||
@@ -1206,6 +1224,49 @@ mod tests {
|
||||
|
||||
assert_eq!(body_a["prompt_cache_key"], body_b["prompt_cache_key"]);
|
||||
assert_ne!(body_a["prompt_cache_key"], body_c["prompt_cache_key"]);
|
||||
assert!(!body_a.to_string().contains("\"cache_control\""));
|
||||
assert!(!body_b.to_string().contains("\"cache_control\""));
|
||||
assert!(!body_c.to_string().contains("\"cache_control\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_responses_body_edits_strip_developer_cache_control_before_upstream() {
|
||||
let mut provider_request_body = json!({
|
||||
"input": [{
|
||||
"type": "message",
|
||||
"role": "developer",
|
||||
"content": [{
|
||||
"type": "input_text",
|
||||
"text": "stable system brief",
|
||||
"cache_control": {"type": "ephemeral"}
|
||||
}]
|
||||
}, {
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [{"type": "input_text", "text": "new turn"}]
|
||||
}],
|
||||
"model": "gpt-5.4"
|
||||
});
|
||||
|
||||
apply_codex_openai_responses_special_body_edits(
|
||||
&mut provider_request_body,
|
||||
"codex",
|
||||
"openai:responses",
|
||||
None,
|
||||
Some("key-a"),
|
||||
);
|
||||
|
||||
assert!(provider_request_body
|
||||
.get("prompt_cache_key")
|
||||
.and_then(|value| value.as_str())
|
||||
.is_some_and(|value| !value.trim().is_empty()));
|
||||
assert!(!provider_request_body
|
||||
.to_string()
|
||||
.contains("\"cache_control\""));
|
||||
assert_eq!(
|
||||
provider_request_body["input"][0]["content"][0]["text"],
|
||||
json!("stable system brief")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2416,7 +2416,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pure_claude_to_openai_chat_maps_max_output_effort_to_xhigh() {
|
||||
fn pure_claude_to_openai_chat_clamps_max_output_effort_to_high() {
|
||||
let body = json!({
|
||||
"model": "claude-sonnet",
|
||||
"messages": [{"role": "user", "content": "hello"}],
|
||||
@@ -2430,7 +2430,7 @@ mod tests {
|
||||
.expect("pure conversion should succeed")
|
||||
.value;
|
||||
|
||||
assert_eq!(converted["reasoning_effort"], "xhigh");
|
||||
assert_eq!(converted["reasoning_effort"], "high");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3256,7 +3256,7 @@ mod tests {
|
||||
)
|
||||
.expect("legacy conversion should still emit a chat body");
|
||||
|
||||
assert_eq!(converted["stream"], true);
|
||||
assert!(converted.get("stream").is_none());
|
||||
assert!(converted.get("include").is_none());
|
||||
assert!(converted.get("previous_response_id").is_none());
|
||||
}
|
||||
|
||||
@@ -44,8 +44,7 @@ impl ReasoningEffort {
|
||||
Self::Low => "low",
|
||||
Self::Medium => "medium",
|
||||
Self::High => "high",
|
||||
Self::XHigh => "xhigh",
|
||||
Self::Max => "xhigh",
|
||||
Self::XHigh | Self::Max => "high",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +534,7 @@ mod tests {
|
||||
"gpt-5.4-xhigh",
|
||||
)
|
||||
.expect("directive should apply");
|
||||
assert_eq!(openai_chat["reasoning_effort"], "xhigh");
|
||||
assert_eq!(openai_chat["reasoning_effort"], "high");
|
||||
|
||||
let mut responses = json!({
|
||||
"model": "gpt-5-upstream",
|
||||
@@ -608,7 +607,7 @@ mod tests {
|
||||
"gpt-5.4-fast-xhigh",
|
||||
)
|
||||
.expect("directive should apply");
|
||||
assert_eq!(openai_chat["reasoning_effort"], "xhigh");
|
||||
assert_eq!(openai_chat["reasoning_effort"], "high");
|
||||
assert_eq!(openai_chat["service_tier"], "priority");
|
||||
|
||||
let mut reversed = json!({"model": "gpt-5-upstream", "reasoning_effort": "low"});
|
||||
|
||||
@@ -738,7 +738,7 @@ mod tests {
|
||||
.expect("openai chat body should build");
|
||||
|
||||
assert_eq!(provider_request_body["model"], "gpt-5-upstream");
|
||||
assert_eq!(provider_request_body["reasoning_effort"], "xhigh");
|
||||
assert_eq!(provider_request_body["reasoning_effort"], "high");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user