mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
Normalize canonical API formats
This commit is contained in:
@@ -594,10 +594,14 @@ pub(crate) fn api_format_display_name(api_format: &str) -> String {
|
||||
};
|
||||
let kind_label = match kind {
|
||||
"chat" => "Chat",
|
||||
"cli" => "CLI",
|
||||
"messages" => "Messages",
|
||||
"generate_content" => "Generate Content",
|
||||
"responses" => "Responses",
|
||||
"responses:compact" => "Responses Compact",
|
||||
"compact" => "Compact",
|
||||
"video" => "Video",
|
||||
"image" => "Image",
|
||||
"files" => "Files",
|
||||
other => other,
|
||||
};
|
||||
format!("{family_label} {kind_label}")
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde_json::json;
|
||||
|
||||
pub(crate) fn build_models_auth_error_response(api_format: &str) -> Response<Body> {
|
||||
match api_format {
|
||||
"claude:chat" => (
|
||||
"claude:messages" => (
|
||||
http::StatusCode::UNAUTHORIZED,
|
||||
Json(json!({
|
||||
"type": "error",
|
||||
@@ -20,7 +20,7 @@ pub(crate) fn build_models_auth_error_response(api_format: &str) -> Response<Bod
|
||||
})),
|
||||
)
|
||||
.into_response(),
|
||||
"gemini:chat" => (
|
||||
"gemini:generate_content" => (
|
||||
http::StatusCode::UNAUTHORIZED,
|
||||
Json(json!({
|
||||
"error": {
|
||||
@@ -48,7 +48,7 @@ pub(crate) fn build_models_auth_error_response(api_format: &str) -> Response<Bod
|
||||
|
||||
pub(super) fn build_models_not_found_response(model_id: &str, api_format: &str) -> Response<Body> {
|
||||
match api_format {
|
||||
"claude:chat" => (
|
||||
"claude:messages" => (
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({
|
||||
"type": "error",
|
||||
@@ -59,7 +59,7 @@ pub(super) fn build_models_not_found_response(model_id: &str, api_format: &str)
|
||||
})),
|
||||
)
|
||||
.into_response(),
|
||||
"gemini:chat" => (
|
||||
"gemini:generate_content" => (
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({
|
||||
"error": {
|
||||
@@ -87,14 +87,14 @@ pub(super) fn build_models_not_found_response(model_id: &str, api_format: &str)
|
||||
|
||||
pub(super) fn build_empty_models_list_response(api_format: &str) -> Response<Body> {
|
||||
match api_format {
|
||||
"claude:chat" => Json(json!({
|
||||
"claude:messages" => Json(json!({
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"first_id": serde_json::Value::Null,
|
||||
"last_id": serde_json::Value::Null,
|
||||
}))
|
||||
.into_response(),
|
||||
"gemini:chat" => Json(json!({ "models": [] })).into_response(),
|
||||
"gemini:generate_content" => Json(json!({ "models": [] })).into_response(),
|
||||
_ => Json(json!({ "object": "list", "data": [] })).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ pub(super) async fn maybe_build_local_models_route_response(
|
||||
return Some(build_empty_models_list_response(api_format));
|
||||
}
|
||||
let response = match api_format {
|
||||
"claude:chat" => {
|
||||
"claude:messages" => {
|
||||
let before_id = query_param_value(
|
||||
request_context.request_query_string.as_deref(),
|
||||
"before_id",
|
||||
@@ -136,7 +136,7 @@ pub(super) async fn maybe_build_local_models_route_response(
|
||||
limit,
|
||||
)
|
||||
}
|
||||
"gemini:chat" => {
|
||||
"gemini:generate_content" => {
|
||||
let page_size = query_param_value(
|
||||
request_context.request_query_string.as_deref(),
|
||||
"pageSize",
|
||||
@@ -167,8 +167,8 @@ pub(super) async fn maybe_build_local_models_route_response(
|
||||
return Some(build_models_not_found_response(&model_id, api_format));
|
||||
};
|
||||
let response = match api_format {
|
||||
"claude:chat" => build_claude_model_detail_response(row),
|
||||
"gemini:chat" => build_gemini_model_detail_response(row),
|
||||
"claude:messages" => build_claude_model_detail_response(row),
|
||||
"gemini:generate_content" => build_gemini_model_detail_response(row),
|
||||
_ => build_openai_model_detail_response(row),
|
||||
};
|
||||
Some(response)
|
||||
|
||||
@@ -12,13 +12,13 @@ pub(crate) fn models_api_format(request_context: &GatewayPublicRequestContext) -
|
||||
.and_then(|decision| decision.auth_endpoint_signature.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|signature| !signature.is_empty())?;
|
||||
match crate::ai_pipeline::normalize_legacy_openai_format_alias(signature).as_str() {
|
||||
match crate::ai_pipeline::normalize_api_format_alias(signature).as_str() {
|
||||
"openai:chat" => Some("openai:chat"),
|
||||
"openai:responses" => Some("openai:responses"),
|
||||
"openai:responses:compact" => Some("openai:responses:compact"),
|
||||
"openai:image" => Some("openai:image"),
|
||||
"claude:chat" => Some("claude:chat"),
|
||||
"gemini:chat" => Some("gemini:chat"),
|
||||
"claude:messages" => Some("claude:messages"),
|
||||
"gemini:generate_content" => Some("gemini:generate_content"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -27,22 +27,18 @@ const MODELS_CROSS_FORMAT_QUERY_API_FORMATS: &[&str] = &[
|
||||
"openai:chat",
|
||||
"openai:responses",
|
||||
"openai:responses:compact",
|
||||
"openai:cli",
|
||||
"openai:compact",
|
||||
"openai:image",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"gemini:chat",
|
||||
"gemini:cli",
|
||||
"claude:messages",
|
||||
"gemini:generate_content",
|
||||
];
|
||||
|
||||
pub(super) fn models_query_api_formats(api_format: &str) -> &'static [&'static str] {
|
||||
match crate::ai_pipeline::normalize_legacy_openai_format_alias(api_format).as_str() {
|
||||
match crate::ai_pipeline::normalize_api_format_alias(api_format).as_str() {
|
||||
"openai:chat"
|
||||
| "openai:responses"
|
||||
| "openai:responses:compact"
|
||||
| "claude:chat"
|
||||
| "gemini:chat" => MODELS_CROSS_FORMAT_QUERY_API_FORMATS,
|
||||
| "claude:messages"
|
||||
| "gemini:generate_content" => MODELS_CROSS_FORMAT_QUERY_API_FORMATS,
|
||||
"openai:image" => &["openai:image"],
|
||||
_ => &[],
|
||||
}
|
||||
|
||||
@@ -90,17 +90,17 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
|
||||
} else {
|
||||
let endpoint = active_endpoints.into_iter().next()?;
|
||||
let format_value = if endpoint.api_format.trim().is_empty() {
|
||||
"claude:chat".to_string()
|
||||
"claude:messages".to_string()
|
||||
} else {
|
||||
endpoint.api_format.clone()
|
||||
crate::ai_pipeline::normalize_api_format_alias(&endpoint.api_format)
|
||||
};
|
||||
(endpoint, format_value)
|
||||
};
|
||||
|
||||
let format_value = format_value.trim().to_string();
|
||||
let format_value = crate::ai_pipeline::normalize_api_format_alias(&format_value);
|
||||
if !matches!(
|
||||
format_value.as_str(),
|
||||
"openai:chat" | "claude:chat" | "gemini:chat"
|
||||
"openai:chat" | "claude:messages" | "gemini:generate_content"
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
@@ -169,12 +169,12 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
|
||||
}
|
||||
|
||||
let mut provider_request_body = match format_value.as_str() {
|
||||
"openai:chat" | "claude:chat" => json!({
|
||||
"openai:chat" | "claude:messages" => json!({
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": "Health check"}],
|
||||
"max_tokens": 5,
|
||||
}),
|
||||
"gemini:chat" => json!({
|
||||
"gemini:generate_content" => json!({
|
||||
"contents": [{
|
||||
"role": "user",
|
||||
"parts": [{"text": "Health check"}],
|
||||
@@ -194,7 +194,7 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
|
||||
}
|
||||
|
||||
let oauth_auth = match format_value.as_str() {
|
||||
"openai:chat" | "claude:chat" => {
|
||||
"openai:chat" | "claude:messages" => {
|
||||
match state.resolve_local_oauth_request_auth(&transport).await {
|
||||
Ok(Some(crate::provider_transport::LocalResolvedOAuthRequestAuth::Header {
|
||||
name,
|
||||
@@ -211,9 +211,13 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
|
||||
crate::provider_transport::auth::resolve_local_openai_bearer_auth(&transport)
|
||||
.or(oauth_auth.clone())
|
||||
}
|
||||
"claude:chat" => crate::provider_transport::auth::resolve_local_standard_auth(&transport)
|
||||
.or(oauth_auth.clone()),
|
||||
"gemini:chat" => crate::provider_transport::auth::resolve_local_gemini_auth(&transport),
|
||||
"claude:messages" => {
|
||||
crate::provider_transport::auth::resolve_local_standard_auth(&transport)
|
||||
.or(oauth_auth.clone())
|
||||
}
|
||||
"gemini:generate_content" => {
|
||||
crate::provider_transport::auth::resolve_local_gemini_auth(&transport)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let Some((auth_header, auth_value)) = auth else {
|
||||
|
||||
@@ -213,13 +213,12 @@ fn users_me_usage_api_format_defaults_to_non_stream(item: &StoredRequestUsageAud
|
||||
return false;
|
||||
};
|
||||
matches!(
|
||||
crate::ai_pipeline::normalize_legacy_openai_format_alias(value).as_str(),
|
||||
crate::ai_pipeline::normalize_api_format_alias(value).as_str(),
|
||||
"openai:chat"
|
||||
| "openai:responses"
|
||||
| "openai:responses:compact"
|
||||
| "openai:image"
|
||||
| "claude:chat"
|
||||
| "claude:cli"
|
||||
| "claude:messages"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1347,9 +1346,9 @@ mod tests {
|
||||
let item = StoredRequestUsageAudit {
|
||||
provider_name: "Claude".to_string(),
|
||||
model: "claude-sonnet-4-5".to_string(),
|
||||
api_format: Some("claude:chat".to_string()),
|
||||
api_format: Some("claude:messages".to_string()),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_api_format: Some("claude:chat".to_string()),
|
||||
endpoint_api_format: Some("claude:messages".to_string()),
|
||||
provider_api_family: Some("claude".to_string()),
|
||||
input_tokens: 4941,
|
||||
output_tokens: 973,
|
||||
|
||||
Reference in New Issue
Block a user