centralize openai responses alias handling

This commit is contained in:
fawney19
2026-04-26 21:47:01 +08:00
parent ea3dc3257e
commit 4ec591fbf2
52 changed files with 364 additions and 396 deletions

View File

@@ -7,6 +7,7 @@ repository.workspace = true
description = "Shared admin contracts and pure helpers for Aether"
[dependencies]
aether-ai-formats.workspace = true
aether-billing.workspace = true
aether-contracts.workspace = true
aether-data.workspace = true

View File

@@ -835,17 +835,17 @@ fn admin_usage_api_format_defaults_to_non_stream(item: &StoredRequestUsageAudit)
.or(item.endpoint_api_format.as_deref())
.map(str::trim)
.filter(|value| !value.is_empty());
let Some(value) = api_format else {
return false;
};
matches!(
api_format,
Some(value)
if value.eq_ignore_ascii_case("openai:chat")
|| value.eq_ignore_ascii_case("openai:responses")
|| value.eq_ignore_ascii_case("openai:responses:compact")
|| value.eq_ignore_ascii_case("openai:cli")
|| value.eq_ignore_ascii_case("openai:compact")
|| value.eq_ignore_ascii_case("openai:image")
|| value.eq_ignore_ascii_case("claude:chat")
|| value.eq_ignore_ascii_case("claude:cli")
aether_ai_formats::normalize_legacy_openai_format_alias(value).as_str(),
"openai:chat"
| "openai:responses"
| "openai:responses:compact"
| "openai:image"
| "claude:chat"
| "claude:cli"
)
}

View File

@@ -86,9 +86,50 @@ impl FromStr for FormatId {
}
}
pub fn normalize_legacy_openai_format_alias(value: &str) -> String {
match value.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses".to_string(),
"openai:compact" => "openai:responses:compact".to_string(),
other => other.to_string(),
}
}
pub fn legacy_openai_format_alias_matches(left: &str, right: &str) -> bool {
normalize_legacy_openai_format_alias(left) == normalize_legacy_openai_format_alias(right)
}
pub fn openai_format_storage_aliases(value: &str) -> Vec<String> {
match normalize_legacy_openai_format_alias(value).as_str() {
"openai:responses" => vec!["openai:responses".to_string(), "openai:cli".to_string()],
"openai:responses:compact" => vec![
"openai:responses:compact".to_string(),
"openai:compact".to_string(),
],
normalized => vec![normalized.to_string()],
}
}
pub fn is_openai_responses_format(value: &str) -> bool {
normalize_legacy_openai_format_alias(value) == "openai:responses"
}
pub fn is_openai_responses_compact_format(value: &str) -> bool {
normalize_legacy_openai_format_alias(value) == "openai:responses:compact"
}
pub fn is_openai_responses_family_format(value: &str) -> bool {
matches!(
normalize_legacy_openai_format_alias(value).as_str(),
"openai:responses" | "openai:responses:compact"
)
}
#[cfg(test)]
mod tests {
use super::FormatId;
use super::{
legacy_openai_format_alias_matches, normalize_legacy_openai_format_alias,
openai_format_storage_aliases, FormatId,
};
#[test]
fn normalizes_legacy_aliases() {
@@ -109,4 +150,43 @@ mod tests {
Some(FormatId::GeminiGenerateContent)
);
}
#[test]
fn normalizes_openai_legacy_aliases_without_rewriting_other_families() {
assert_eq!(
normalize_legacy_openai_format_alias(" openai:cli "),
"openai:responses"
);
assert_eq!(
normalize_legacy_openai_format_alias("OPENAI:COMPACT"),
"openai:responses:compact"
);
assert_eq!(
normalize_legacy_openai_format_alias("claude:cli"),
"claude:cli"
);
assert!(legacy_openai_format_alias_matches(
"openai:cli",
"openai:responses"
));
}
#[test]
fn storage_aliases_include_legacy_openai_rows_only() {
assert_eq!(
openai_format_storage_aliases("openai:responses"),
vec!["openai:responses".to_string(), "openai:cli".to_string()]
);
assert_eq!(
openai_format_storage_aliases("openai:compact"),
vec![
"openai:responses:compact".to_string(),
"openai:compact".to_string()
]
);
assert_eq!(
openai_format_storage_aliases("claude:cli"),
vec!["claude:cli".to_string()]
);
}
}

View File

@@ -22,7 +22,12 @@ pub use canonical::{
CanonicalRole, CanonicalStopReason, CanonicalStreamEvent, CanonicalStreamFrame,
CanonicalThinkingConfig, CanonicalToolChoice, CanonicalToolDefinition, CanonicalUsage,
};
pub use formats::{FormatFamily, FormatId, FormatProfile};
pub use formats::{
is_openai_responses_compact_format, is_openai_responses_family_format,
is_openai_responses_format, legacy_openai_format_alias_matches,
normalize_legacy_openai_format_alias, openai_format_storage_aliases, FormatFamily, FormatId,
FormatProfile,
};
pub use registry::{
build_stream_transcoder, convert_request, convert_response, FormatContext, FormatError,
};

View File

@@ -189,3 +189,8 @@ pub use crate::planner::standard::{
CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_MODEL, CODEX_OPENAI_IMAGE_DEFAULT_VARIATION_PROMPT,
CODEX_OPENAI_IMAGE_INTERNAL_MODEL,
};
pub use aether_ai_formats::{
is_openai_responses_compact_format, is_openai_responses_family_format,
is_openai_responses_format, legacy_openai_format_alias_matches,
normalize_legacy_openai_format_alias, openai_format_storage_aliases,
};

View File

@@ -37,12 +37,8 @@ pub fn build_core_error_body_for_client_format(
let mut error_object = Map::new();
error_object.insert("message".to_string(), Value::String(message.to_string()));
match client_api_format.trim().to_ascii_lowercase().as_str() {
"openai:chat"
| "openai:responses"
| "openai:cli"
| "openai:compact"
| "openai:responses:compact" => {
match aether_ai_formats::normalize_legacy_openai_format_alias(client_api_format).as_str() {
"openai:chat" | "openai:responses" | "openai:responses:compact" => {
error_object.insert(
"type".to_string(),
Value::String(map_local_sync_error_kind_to_openai_type(kind).to_string()),

View File

@@ -1,5 +1,9 @@
#![allow(dead_code)]
use aether_ai_formats::{
is_openai_responses_compact_format, legacy_openai_format_alias_matches,
normalize_legacy_openai_format_alias,
};
use aether_provider_transport::auth::{
resolve_local_gemini_auth, resolve_local_openai_bearer_auth, resolve_local_standard_auth,
};
@@ -52,27 +56,18 @@ pub fn request_candidate_api_format_preference(
client_api_format: &str,
provider_api_format: &str,
) -> Option<(u8, u8)> {
let client_api_format = client_api_format.trim().to_ascii_lowercase();
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
if matches!(
client_api_format.as_str(),
"openai:compact" | "openai:responses:compact"
) {
return matches!(
provider_api_format.as_str(),
"openai:compact" | "openai:responses:compact"
)
.then_some((0, 0));
if client_api_format == "openai:responses:compact" {
return (provider_api_format == "openai:responses:compact").then_some((0, 0));
}
let (client_family, client_kind) =
parse_non_compact_standard_api_format(client_api_format.as_str())?;
let (provider_family, provider_kind) =
parse_non_compact_standard_api_format(provider_api_format.as_str())?;
let preference_bucket = if canonical_standard_api_format(client_api_format.as_str())
== canonical_standard_api_format(provider_api_format.as_str())
{
let preference_bucket = if client_api_format == provider_api_format {
0
} else if client_kind == provider_kind {
1
@@ -92,11 +87,8 @@ pub fn request_candidate_api_formats(
client_api_format: &str,
_require_streaming: bool,
) -> Vec<&'static str> {
let client_api_format = client_api_format.trim().to_ascii_lowercase();
if matches!(
client_api_format.as_str(),
"openai:compact" | "openai:responses:compact"
) {
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
if client_api_format == "openai:responses:compact" {
return vec!["openai:responses:compact"];
}
if parse_non_compact_standard_api_format(client_api_format.as_str()).is_none() {
@@ -115,33 +107,25 @@ pub fn request_conversion_kind(
client_api_format: &str,
provider_api_format: &str,
) -> Option<RequestConversionKind> {
let client_api_format = client_api_format.trim().to_ascii_lowercase();
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
if client_api_format == provider_api_format {
return None;
}
if normalized_same_standard_api_format(client_api_format.as_str(), provider_api_format.as_str())
{
return None;
}
if !is_standard_api_format(client_api_format.as_str())
|| !is_standard_api_format(provider_api_format.as_str())
{
return None;
}
if matches!(
client_api_format.as_str(),
"openai:compact" | "openai:responses:compact"
) || matches!(
provider_api_format.as_str(),
"openai:compact" | "openai:responses:compact"
) {
if is_openai_responses_compact_format(client_api_format.as_str())
|| is_openai_responses_compact_format(provider_api_format.as_str())
{
return None;
}
match provider_api_format.as_str() {
"openai:chat" => Some(RequestConversionKind::ToOpenAIChat),
"openai:responses" | "openai:cli" => Some(RequestConversionKind::ToOpenAiResponses),
"openai:responses" => Some(RequestConversionKind::ToOpenAiResponses),
"claude:chat" | "claude:cli" => Some(RequestConversionKind::ToClaudeStandard),
"gemini:chat" | "gemini:cli" => Some(RequestConversionKind::ToGeminiStandard),
_ => None,
@@ -152,15 +136,11 @@ pub fn sync_chat_response_conversion_kind(
provider_api_format: &str,
client_api_format: &str,
) -> Option<SyncChatResponseConversionKind> {
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
let client_api_format = client_api_format.trim().to_ascii_lowercase();
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
if provider_api_format == client_api_format {
return None;
}
if normalized_same_standard_api_format(provider_api_format.as_str(), client_api_format.as_str())
{
return None;
}
if !is_standard_api_format(provider_api_format.as_str()) {
return None;
}
@@ -177,26 +157,19 @@ pub fn sync_cli_response_conversion_kind(
provider_api_format: &str,
client_api_format: &str,
) -> Option<SyncCliResponseConversionKind> {
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
let client_api_format = client_api_format.trim().to_ascii_lowercase();
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
if provider_api_format == client_api_format {
return None;
}
if normalized_same_standard_api_format(provider_api_format.as_str(), client_api_format.as_str())
{
return None;
}
if !is_standard_api_format(provider_api_format.as_str()) {
return None;
}
if !matches!(
client_api_format.as_str(),
"openai:compact" | "openai:responses:compact"
) {
if !is_openai_responses_compact_format(client_api_format.as_str()) {
request_conversion_kind(client_api_format.as_str(), provider_api_format.as_str())?;
}
match client_api_format.as_str() {
"openai:responses" | "openai:cli" | "openai:compact" | "openai:responses:compact" => {
"openai:responses" | "openai:responses:compact" => {
Some(SyncCliResponseConversionKind::ToOpenAiResponses)
}
"claude:cli" => Some(SyncCliResponseConversionKind::ToClaudeCli),
@@ -209,8 +182,8 @@ pub fn request_conversion_requires_enable_flag(
client_api_format: &str,
provider_api_format: &str,
) -> bool {
let client_api_format = client_api_format.trim().to_ascii_lowercase();
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
match (
api_data_format_id(client_api_format.as_str()),
api_data_format_id(provider_api_format.as_str()),
@@ -227,8 +200,8 @@ pub fn request_conversion_enabled_for_transport(
client_api_format: &str,
provider_api_format: &str,
) -> bool {
let client_api_format = client_api_format.trim().to_ascii_lowercase();
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
if client_api_format == provider_api_format {
return true;
}
@@ -250,8 +223,8 @@ pub fn request_pair_allowed_for_transport(
client_api_format: &str,
provider_api_format: &str,
) -> bool {
let client_api_format = client_api_format.trim().to_ascii_lowercase();
let provider_api_format = provider_api_format.trim().to_ascii_lowercase();
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
if client_api_format == provider_api_format {
return true;
}
@@ -304,15 +277,9 @@ pub fn request_conversion_transport_unsupported_reason(
return local_kiro_request_transport_unsupported_reason_with_network(transport);
}
match transport
.endpoint
.api_format
.trim()
.to_ascii_lowercase()
.as_str()
{
match normalize_legacy_openai_format_alias(&transport.endpoint.api_format).as_str() {
"openai:chat" => local_openai_chat_transport_unsupported_reason(transport),
"openai:responses" | "openai:cli" | "openai:responses:compact" | "openai:compact" => {
"openai:responses" | "openai:responses:compact" => {
local_standard_transport_unsupported_reason_with_network(
transport,
transport.endpoint.api_format.trim(),
@@ -341,18 +308,10 @@ pub fn request_conversion_direct_auth(
transport: &GatewayProviderTransportSnapshot,
_kind: RequestConversionKind,
) -> Option<(String, String)> {
match transport
.endpoint
.api_format
.trim()
.to_ascii_lowercase()
.as_str()
{
"openai:chat"
| "openai:responses"
| "openai:cli"
| "openai:compact"
| "openai:responses:compact" => resolve_local_openai_bearer_auth(transport),
match normalize_legacy_openai_format_alias(&transport.endpoint.api_format).as_str() {
"openai:chat" | "openai:responses" | "openai:responses:compact" => {
resolve_local_openai_bearer_auth(transport)
}
"gemini:chat" | "gemini:cli" => {
if is_vertex_api_key_transport_context(transport) {
resolve_local_vertex_api_key_query_auth(transport)
@@ -368,11 +327,9 @@ pub fn request_conversion_direct_auth(
fn is_standard_api_format(api_format: &str) -> bool {
matches!(
api_format,
normalize_legacy_openai_format_alias(api_format).as_str(),
"openai:chat"
| "openai:responses"
| "openai:cli"
| "openai:compact"
| "openai:responses:compact"
| "claude:chat"
| "claude:cli"
@@ -381,15 +338,16 @@ fn is_standard_api_format(api_format: &str) -> bool {
)
}
fn parse_non_compact_standard_api_format(api_format: &str) -> Option<(&str, &str)> {
let (family, kind) = api_format.split_once(':')?;
if family == "openai" && kind == "responses" {
return Some((family, "cli"));
fn parse_non_compact_standard_api_format(api_format: &str) -> Option<(&'static str, &'static str)> {
match normalize_legacy_openai_format_alias(api_format).as_str() {
"openai:chat" => Some(("openai", "chat")),
"openai:responses" => Some(("openai", "cli")),
"claude:chat" => Some(("claude", "chat")),
"claude:cli" => Some(("claude", "cli")),
"gemini:chat" => Some(("gemini", "chat")),
"gemini:cli" => Some(("gemini", "cli")),
_ => None,
}
if !STANDARD_API_FAMILY_ORDER.contains(&family) || !matches!(kind, "chat" | "cli") {
return None;
}
Some((family, kind))
}
fn standard_api_family_priority(family: &str) -> u8 {
@@ -400,33 +358,17 @@ fn standard_api_family_priority(family: &str) -> u8 {
}
fn api_data_format_id(api_format: &str) -> Option<&'static str> {
match api_format {
match normalize_legacy_openai_format_alias(api_format).as_str() {
"claude:chat" | "claude:cli" => Some("claude"),
"gemini:chat" | "gemini:cli" => Some("gemini"),
"openai:chat" => Some("openai_chat"),
"openai:responses" | "openai:cli" | "openai:compact" | "openai:responses:compact" => {
Some("openai_responses")
}
"openai:responses" | "openai:responses:compact" => Some("openai_responses"),
_ => None,
}
}
fn normalized_same_standard_api_format(left: &str, right: &str) -> bool {
matches!(
(left, right),
("openai:responses", "openai:cli")
| ("openai:cli", "openai:responses")
| ("openai:responses:compact", "openai:compact")
| ("openai:compact", "openai:responses:compact")
)
}
fn canonical_standard_api_format(api_format: &str) -> &str {
match api_format {
"openai:cli" => "openai:responses",
"openai:compact" => "openai:responses:compact",
other => other,
}
legacy_openai_format_alias_matches(left, right)
}
fn endpoint_accepts_client_api_format(

View File

@@ -83,12 +83,10 @@ fn supports_standard_stream_rewrite(provider_api_format: &str, client_api_format
fn is_standard_provider_api_format(api_format: &str) -> bool {
matches!(
api_format,
aether_ai_formats::normalize_legacy_openai_format_alias(api_format).as_str(),
"openai:chat"
| "openai:responses"
| "openai:responses:compact"
| "openai:cli"
| "openai:compact"
| "claude:chat"
| "claude:cli"
| "gemini:chat"
@@ -102,13 +100,8 @@ fn is_standard_chat_client_api_format(api_format: &str) -> bool {
fn is_standard_cli_client_api_format(api_format: &str) -> bool {
matches!(
api_format,
"openai:responses"
| "openai:responses:compact"
| "openai:cli"
| "openai:compact"
| "claude:cli"
| "gemini:cli"
aether_ai_formats::normalize_legacy_openai_format_alias(api_format).as_str(),
"openai:responses" | "openai:responses:compact" | "claude:cli" | "gemini:cli"
)
}

View File

@@ -622,9 +622,9 @@ pub fn aggregate_standard_chat_stream_sync_response(
body: &[u8],
provider_api_format: &str,
) -> Option<Value> {
match provider_api_format.trim().to_ascii_lowercase().as_str() {
match aether_ai_formats::normalize_legacy_openai_format_alias(provider_api_format).as_str() {
"openai:chat" => aggregate_openai_chat_stream_sync_response(body),
"openai:responses" | "openai:cli" | "openai:compact" | "openai:responses:compact" => {
"openai:responses" | "openai:responses:compact" => {
aggregate_openai_responses_stream_sync_response(body)
}
"claude:chat" | "claude:cli" => aggregate_claude_stream_sync_response(body),
@@ -1255,11 +1255,7 @@ fn is_openai_responses_family_api_format(api_format: &str) -> bool {
}
fn normalize_openai_responses_family_api_format(api_format: &str) -> String {
match api_format.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses".to_string(),
"openai:compact" => "openai:responses:compact".to_string(),
other => other.to_string(),
}
aether_ai_formats::normalize_legacy_openai_format_alias(api_format)
}
fn parse_stream_json_events(body: &[u8]) -> Option<Vec<Value>> {

View File

@@ -26,10 +26,7 @@ pub fn force_upstream_streaming_for_provider(
provider_api_format: &str,
) -> bool {
provider_type.trim().eq_ignore_ascii_case("codex")
&& matches!(
provider_api_format.trim().to_ascii_lowercase().as_str(),
"openai:responses" | "openai:cli"
)
&& aether_ai_formats::is_openai_responses_format(provider_api_format)
}
#[cfg(test)]

View File

@@ -27,21 +27,12 @@ const UUID_NAMESPACE_OID_BYTES: [u8; 16] = [
fn is_codex_openai_responses_request(provider_type: &str, provider_api_format: &str) -> bool {
provider_type.trim().eq_ignore_ascii_case("codex")
&& matches!(
provider_api_format.trim().to_ascii_lowercase().as_str(),
"openai:responses"
| "openai:responses:compact"
| "openai:cli"
| "openai:compact"
| "openai:image"
)
&& (aether_ai_formats::is_openai_responses_family_format(provider_api_format)
|| is_openai_image_request(provider_api_format))
}
fn is_openai_responses_compact_request(provider_api_format: &str) -> bool {
matches!(
provider_api_format.trim().to_ascii_lowercase().as_str(),
"openai:responses:compact" | "openai:compact"
)
aether_ai_formats::is_openai_responses_compact_format(provider_api_format)
}
fn is_openai_image_request(provider_api_format: &str) -> bool {
@@ -413,10 +404,8 @@ pub fn apply_codex_openai_responses_special_headers(
}
}
if matches!(
provider_api_format.trim().to_ascii_lowercase().as_str(),
"openai:responses" | "openai:cli"
) && !header_map_has_non_empty_value(original_headers, "conversation_id")
if aether_ai_formats::is_openai_responses_format(provider_api_format)
&& !header_map_has_non_empty_value(original_headers, "conversation_id")
&& !btree_map_has_non_empty_value(provider_request_headers, "conversation_id")
{
if let Some(short_session_id) = short_session_id.as_deref() {

View File

@@ -67,28 +67,24 @@ pub fn build_standard_request_body_from_canonical(
provider_api_format: &str,
upstream_is_stream: bool,
) -> Option<Value> {
match provider_api_format.trim().to_ascii_lowercase().as_str() {
match aether_ai_formats::normalize_legacy_openai_format_alias(provider_api_format).as_str() {
"openai:chat" => build_local_openai_chat_request_body(
canonical_request,
mapped_model,
upstream_is_stream,
),
"openai:responses" | "openai:cli" => {
convert_openai_chat_request_to_openai_responses_request(
canonical_request,
mapped_model,
upstream_is_stream,
false,
)
}
"openai:responses:compact" | "openai:compact" => {
convert_openai_chat_request_to_openai_responses_request(
canonical_request,
mapped_model,
false,
true,
)
}
"openai:responses" => convert_openai_chat_request_to_openai_responses_request(
canonical_request,
mapped_model,
upstream_is_stream,
false,
),
"openai:responses:compact" => convert_openai_chat_request_to_openai_responses_request(
canonical_request,
mapped_model,
false,
true,
),
"claude:chat" | "claude:cli" => convert_openai_chat_request_to_claude_request(
canonical_request,
mapped_model,
@@ -121,9 +117,9 @@ fn normalize_standard_request_to_openai_chat_request_cow<'a>(
client_api_format: &str,
request_path: &str,
) -> Option<Cow<'a, Value>> {
match client_api_format.trim().to_ascii_lowercase().as_str() {
match aether_ai_formats::normalize_legacy_openai_format_alias(client_api_format).as_str() {
"openai:chat" => Some(Cow::Borrowed(body_json)),
"openai:responses" | "openai:cli" | "openai:responses:compact" | "openai:compact" => {
"openai:responses" | "openai:responses:compact" => {
normalize_openai_responses_request_to_openai_chat_request(body_json).map(Cow::Owned)
}
"claude:chat" | "claude:cli" => {

View File

@@ -7,6 +7,7 @@ repository.workspace = true
description = "Shared data contracts and repository traits for Aether Rust services"
[dependencies]
aether-ai-formats.workspace = true
async-trait.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -58,11 +58,7 @@ impl StoredMinimalCandidateSelectionRow {
}
fn normalize_api_format(value: &str) -> String {
match value.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses".to_string(),
"openai:compact" => "openai:responses:compact".to_string(),
other => other.to_string(),
}
aether_ai_formats::normalize_legacy_openai_format_alias(value)
}
fn api_format_matches(left: &str, right: &str) -> bool {

View File

@@ -7,6 +7,7 @@ repository.workspace = true
description = "Shared data access contracts and config for Aether Rust services"
[dependencies]
aether-ai-formats.workspace = true
aether-data-contracts.workspace = true
aether-cache.workspace = true
aether-wallet.workspace = true

View File

@@ -70,11 +70,7 @@ impl MinimalCandidateSelectionReadRepository for InMemoryMinimalCandidateSelecti
}
fn normalize_api_format(value: &str) -> String {
match value.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses".to_string(),
"openai:compact" => "openai:responses:compact".to_string(),
other => other.to_string(),
}
aether_ai_formats::normalize_legacy_openai_format_alias(value)
}
fn api_format_matches(left: &str, right: &str) -> bool {

View File

@@ -264,14 +264,8 @@ impl SqlxMinimalCandidateSelectionReadRepository {
}
}
fn api_format_aliases(api_format: &str) -> Vec<&str> {
match api_format.trim().to_ascii_lowercase().as_str() {
"openai:responses" => vec!["openai:responses", "openai:cli"],
"openai:cli" => vec!["openai:responses", "openai:cli"],
"openai:responses:compact" => vec!["openai:responses:compact", "openai:compact"],
"openai:compact" => vec!["openai:responses:compact", "openai:compact"],
_ => vec![api_format],
}
fn api_format_aliases(api_format: &str) -> Vec<String> {
aether_ai_formats::openai_format_storage_aliases(api_format)
}
fn dedupe_candidate_selection_rows(

View File

@@ -6,6 +6,7 @@ license.workspace = true
repository.workspace = true
[dependencies]
aether-ai-formats.workspace = true
aether-contracts.workspace = true
aether-data-contracts.workspace = true
aether-provider-transport.workspace = true

View File

@@ -570,11 +570,7 @@ fn wildcard_matches(pattern: &str, model_id: &str) -> bool {
}
fn normalize_api_format(value: &str) -> String {
match value.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses".to_string(),
"openai:compact" => "openai:responses:compact".to_string(),
other => other.to_string(),
}
aether_ai_formats::normalize_legacy_openai_format_alias(value)
}
#[cfg(test)]

View File

@@ -381,14 +381,12 @@ fn apply_fetch_header_rules(
}
fn standard_models_fetch_headers(api_format: &str) -> BTreeMap<String, String> {
let api_format = api_format.trim().to_ascii_lowercase();
let api_format = aether_ai_formats::normalize_legacy_openai_format_alias(api_format);
match api_format.as_str() {
"openai:responses" | "openai:responses:compact" | "openai:cli" | "openai:compact" => {
BTreeMap::from([(
"user-agent".to_string(),
OPENAI_RESPONSES_USER_AGENT.to_string(),
)])
}
"openai:responses" | "openai:responses:compact" => BTreeMap::from([(
"user-agent".to_string(),
OPENAI_RESPONSES_USER_AGENT.to_string(),
)]),
"claude:chat" => BTreeMap::from([(
"anthropic-version".to_string(),
CLAUDE_VERSION_HEADER.to_string(),

View File

@@ -200,13 +200,5 @@ fn local_same_format_transport_unsupported_reason(
}
fn same_api_format(left: &str, right: &str) -> bool {
normalize_api_format_alias(left) == normalize_api_format_alias(right)
}
fn normalize_api_format_alias(value: &str) -> String {
match value.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses".to_string(),
"openai:compact" => "openai:responses:compact".to_string(),
other => other.to_string(),
}
aether_ai_formats::legacy_openai_format_alias_matches(left, right)
}

View File

@@ -181,15 +181,11 @@ pub fn fixed_provider_endpoint_template_by_api_format(
provider_type: &str,
api_format: &str,
) -> Option<&'static FixedProviderEndpointTemplate> {
let normalized = match api_format.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses",
"openai:compact" => "openai:responses:compact",
_ => api_format.trim(),
};
let normalized = aether_ai_formats::normalize_legacy_openai_format_alias(api_format);
fixed_provider_template(provider_type)?
.endpoints
.iter()
.find(|item| item.api_format.eq_ignore_ascii_case(normalized))
.find(|item| item.api_format.eq_ignore_ascii_case(&normalized))
}
pub fn provider_type_supports_model_fetch(provider_type: &str) -> bool {

View File

@@ -60,17 +60,19 @@ pub fn build_transport_request_url(
));
}
let url = match provider_api_format.as_str() {
let url = match aether_ai_formats::normalize_legacy_openai_format_alias(&provider_api_format)
.as_str()
{
"openai:chat" => Some(build_openai_chat_url(
&transport.endpoint.base_url,
params.request_query,
)),
"openai:responses" | "openai:cli" => Some(build_openai_responses_url(
"openai:responses" => Some(build_openai_responses_url(
&transport.endpoint.base_url,
params.request_query,
false,
)),
"openai:responses:compact" | "openai:compact" => Some(build_openai_responses_url(
"openai:responses:compact" => Some(build_openai_responses_url(
&transport.endpoint.base_url,
params.request_query,
true,

View File

@@ -7,6 +7,7 @@ repository.workspace = true
description = "Pure scheduler health and quota logic extracted from aether-gateway"
[dependencies]
aether-ai-formats.workspace = true
aether-contracts.workspace = true
aether-data-contracts.workspace = true
aether-wallet.workspace = true

View File

@@ -253,11 +253,7 @@ pub fn extract_global_priority_for_format(
}
pub fn normalize_api_format(value: &str) -> String {
match value.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses".to_string(),
"openai:compact" => "openai:responses:compact".to_string(),
other => other.to_string(),
}
aether_ai_formats::normalize_legacy_openai_format_alias(value)
}
fn row_has_candidate_model_name(

View File

@@ -7,6 +7,7 @@ repository.workspace = true
description = "Usage runtime shared types and queue primitives extracted from aether-gateway"
[dependencies]
aether-ai-formats.workspace = true
aether-contracts.workspace = true
aether-data.workspace = true
aether-data-contracts.workspace = true

View File

@@ -121,18 +121,18 @@ pub fn infer_internal_finalize_signature(payload: &GatewaySyncReportRequest) ->
}
pub fn resolve_internal_finalize_route(signature: &str) -> Option<InternalFinalizeRoute> {
match signature {
match aether_ai_formats::normalize_legacy_openai_format_alias(signature).as_str() {
"openai:chat" => Some(InternalFinalizeRoute {
public_path: "/v1/chat/completions",
route_family: "openai",
route_kind: "chat",
}),
"openai:responses" | "openai:cli" => Some(InternalFinalizeRoute {
"openai:responses" => Some(InternalFinalizeRoute {
public_path: "/v1/responses",
route_family: "openai",
route_kind: "responses",
}),
"openai:responses:compact" | "openai:compact" => Some(InternalFinalizeRoute {
"openai:responses:compact" => Some(InternalFinalizeRoute {
public_path: "/v1/responses/compact",
route_family: "openai",
route_kind: "responses:compact",