mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
Normalize canonical API formats
This commit is contained in:
@@ -839,13 +839,12 @@ fn admin_usage_api_format_defaults_to_non_stream(item: &StoredRequestUsageAudit)
|
||||
return false;
|
||||
};
|
||||
matches!(
|
||||
aether_ai_formats::normalize_legacy_openai_format_alias(value).as_str(),
|
||||
aether_ai_formats::normalize_api_format_alias(value).as_str(),
|
||||
"openai:chat"
|
||||
| "openai:responses"
|
||||
| "openai:responses:compact"
|
||||
| "openai:image"
|
||||
| "claude:chat"
|
||||
| "claude:cli"
|
||||
| "claude:messages"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -533,13 +533,13 @@ const ADMIN_API_FORMAT_DEFINITIONS: &[AdminApiFormatDefinition] = &[
|
||||
value: "openai:responses",
|
||||
label: "OpenAI Responses",
|
||||
default_path: "/v1/responses",
|
||||
aliases: &["openai_cli", "openai:cli", "responses"],
|
||||
aliases: &["responses"],
|
||||
},
|
||||
AdminApiFormatDefinition {
|
||||
value: "openai:responses:compact",
|
||||
label: "OpenAI Responses Compact",
|
||||
default_path: "/v1/responses/compact",
|
||||
aliases: &["openai_compact", "openai:compact", "responses_compact"],
|
||||
aliases: &["responses_compact"],
|
||||
},
|
||||
AdminApiFormatDefinition {
|
||||
value: "openai:image",
|
||||
@@ -554,29 +554,17 @@ const ADMIN_API_FORMAT_DEFINITIONS: &[AdminApiFormatDefinition] = &[
|
||||
aliases: &["openai_video", "sora"],
|
||||
},
|
||||
AdminApiFormatDefinition {
|
||||
value: "claude:chat",
|
||||
label: "Claude Chat",
|
||||
value: "claude:messages",
|
||||
label: "Claude Messages",
|
||||
default_path: "/v1/messages",
|
||||
aliases: &["claude", "anthropic", "claude_compatible"],
|
||||
},
|
||||
AdminApiFormatDefinition {
|
||||
value: "claude:cli",
|
||||
label: "Claude CLI",
|
||||
default_path: "/v1/messages",
|
||||
aliases: &["claude_cli", "claude-cli"],
|
||||
},
|
||||
AdminApiFormatDefinition {
|
||||
value: "gemini:chat",
|
||||
label: "Gemini Chat",
|
||||
value: "gemini:generate_content",
|
||||
label: "Gemini Generate Content",
|
||||
default_path: "/v1beta/models/{model}:{action}",
|
||||
aliases: &["gemini", "google", "vertex"],
|
||||
},
|
||||
AdminApiFormatDefinition {
|
||||
value: "gemini:cli",
|
||||
label: "Gemini CLI",
|
||||
default_path: "/v1beta/models/{model}:{action}",
|
||||
aliases: &["gemini_cli", "gemini-cli"],
|
||||
},
|
||||
AdminApiFormatDefinition {
|
||||
value: "gemini:video",
|
||||
label: "Gemini Video",
|
||||
@@ -1994,7 +1982,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn resolve_admin_system_export_key_api_formats_uses_endpoint_fallback() {
|
||||
let provider_formats = vec!["openai:chat".to_string(), "claude:chat".to_string()];
|
||||
let provider_formats = vec!["openai:chat".to_string(), "claude:messages".to_string()];
|
||||
let resolved =
|
||||
resolve_admin_system_export_key_api_formats(None, &provider_formats, |value| {
|
||||
Some(value.to_string())
|
||||
|
||||
@@ -82,55 +82,40 @@ impl FromStr for FormatId {
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"openai" | "openai:chat" | "/v1/chat/completions" => Ok(Self::OpenAiChat),
|
||||
"openai:responses" | "openai:cli" | "/v1/responses" => Ok(Self::OpenAiResponses),
|
||||
"openai:responses:compact" | "openai:compact" | "/v1/responses/compact" => {
|
||||
"openai:responses" | "/v1/responses" => Ok(Self::OpenAiResponses),
|
||||
"openai:responses:compact" | "/v1/responses/compact" => {
|
||||
Ok(Self::OpenAiResponsesCompact)
|
||||
}
|
||||
"claude:messages" | "claude:chat" | "claude:cli" | "/v1/messages" => {
|
||||
Ok(Self::ClaudeMessages)
|
||||
}
|
||||
"gemini:generate_content" | "gemini:chat" | "gemini:cli" => {
|
||||
Ok(Self::GeminiGenerateContent)
|
||||
}
|
||||
"claude:messages" | "/v1/messages" => Ok(Self::ClaudeMessages),
|
||||
"gemini:generate_content" => Ok(Self::GeminiGenerateContent),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 normalize_api_format_alias(value: &str) -> String {
|
||||
value.trim().to_ascii_lowercase()
|
||||
}
|
||||
|
||||
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 api_format_alias_matches(left: &str, right: &str) -> bool {
|
||||
normalize_api_format_alias(left) == normalize_api_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 api_format_storage_aliases(value: &str) -> Vec<String> {
|
||||
vec![normalize_api_format_alias(value)]
|
||||
}
|
||||
|
||||
pub fn is_openai_responses_format(value: &str) -> bool {
|
||||
normalize_legacy_openai_format_alias(value) == "openai:responses"
|
||||
normalize_api_format_alias(value) == "openai:responses"
|
||||
}
|
||||
|
||||
pub fn is_openai_responses_compact_format(value: &str) -> bool {
|
||||
normalize_legacy_openai_format_alias(value) == "openai:responses:compact"
|
||||
normalize_api_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(),
|
||||
normalize_api_format_alias(value).as_str(),
|
||||
"openai:responses" | "openai:responses:compact"
|
||||
)
|
||||
}
|
||||
@@ -138,66 +123,66 @@ pub fn is_openai_responses_family_format(value: &str) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
legacy_openai_format_alias_matches, normalize_legacy_openai_format_alias,
|
||||
openai_format_storage_aliases, FormatId,
|
||||
api_format_alias_matches, api_format_storage_aliases, normalize_api_format_alias, FormatId,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn normalizes_legacy_aliases() {
|
||||
assert_eq!(
|
||||
FormatId::parse("openai:cli"),
|
||||
Some(FormatId::OpenAiResponses)
|
||||
);
|
||||
assert_eq!(
|
||||
FormatId::parse("openai:compact"),
|
||||
Some(FormatId::OpenAiResponsesCompact)
|
||||
);
|
||||
assert_eq!(
|
||||
FormatId::parse("claude:cli"),
|
||||
Some(FormatId::ClaudeMessages)
|
||||
);
|
||||
assert_eq!(
|
||||
FormatId::parse("gemini:chat"),
|
||||
Some(FormatId::GeminiGenerateContent)
|
||||
);
|
||||
fn retired_api_formats_do_not_parse() {
|
||||
assert_eq!(FormatId::parse("openai:cli"), None);
|
||||
assert_eq!(FormatId::parse("openai:compact"), None);
|
||||
assert_eq!(FormatId::parse("claude:chat"), None);
|
||||
assert_eq!(FormatId::parse("claude:cli"), None);
|
||||
assert_eq!(FormatId::parse("gemini:chat"), None);
|
||||
assert_eq!(FormatId::parse("gemini:cli"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalizes_openai_legacy_aliases_without_rewriting_other_families() {
|
||||
fn normalizes_api_format_aliases() {
|
||||
assert_eq!(
|
||||
normalize_legacy_openai_format_alias(" openai:cli "),
|
||||
normalize_api_format_alias(" OPENAI:RESPONSES "),
|
||||
"openai:responses"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_legacy_openai_format_alias("OPENAI:COMPACT"),
|
||||
normalize_api_format_alias("OPENAI:RESPONSES:COMPACT"),
|
||||
"openai:responses:compact"
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_legacy_openai_format_alias("claude:cli"),
|
||||
"claude:cli"
|
||||
normalize_api_format_alias("CLAUDE:MESSAGES"),
|
||||
"claude:messages"
|
||||
);
|
||||
assert!(legacy_openai_format_alias_matches(
|
||||
"openai:cli",
|
||||
"openai:responses"
|
||||
assert_eq!(
|
||||
normalize_api_format_alias("GEMINI:GENERATE_CONTENT"),
|
||||
"gemini:generate_content"
|
||||
);
|
||||
assert_eq!(normalize_api_format_alias("openai:image"), "openai:image");
|
||||
assert_eq!(normalize_api_format_alias("openai:video"), "openai:video");
|
||||
assert_eq!(normalize_api_format_alias("gemini:video"), "gemini:video");
|
||||
assert_eq!(normalize_api_format_alias("gemini:files"), "gemini:files");
|
||||
assert!(!api_format_alias_matches("claude:cli", "claude:messages"));
|
||||
assert!(!api_format_alias_matches(
|
||||
"gemini:chat",
|
||||
"gemini:generate_content"
|
||||
));
|
||||
assert!(!api_format_alias_matches("openai:cli", "openai:responses"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_aliases_include_legacy_openai_rows_only() {
|
||||
fn storage_aliases_only_include_normalized_value() {
|
||||
assert_eq!(
|
||||
openai_format_storage_aliases("openai:responses"),
|
||||
vec!["openai:responses".to_string(), "openai:cli".to_string()]
|
||||
api_format_storage_aliases("openai:responses"),
|
||||
vec!["openai:responses".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
openai_format_storage_aliases("openai:compact"),
|
||||
vec![
|
||||
"openai:responses:compact".to_string(),
|
||||
"openai:compact".to_string()
|
||||
]
|
||||
api_format_storage_aliases("openai:responses:compact"),
|
||||
vec!["openai:responses:compact".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
openai_format_storage_aliases("claude:cli"),
|
||||
vec!["claude:cli".to_string()]
|
||||
api_format_storage_aliases("claude:messages"),
|
||||
vec!["claude:messages".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
api_format_storage_aliases("gemini:generate_content"),
|
||||
vec!["gemini:generate_content".to_string()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,9 @@ pub use canonical::{
|
||||
};
|
||||
pub use context::{FormatContext, FormatError};
|
||||
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,
|
||||
api_format_alias_matches, api_format_storage_aliases, is_openai_responses_compact_format,
|
||||
is_openai_responses_family_format, is_openai_responses_format, normalize_api_format_alias,
|
||||
FormatFamily, FormatId, FormatProfile,
|
||||
};
|
||||
pub use matrix::{
|
||||
request_candidate_api_format_preference, request_candidate_api_formats,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
formats::{is_openai_responses_compact_format, normalize_legacy_openai_format_alias},
|
||||
legacy_openai_format_alias_matches,
|
||||
api_format_alias_matches,
|
||||
formats::{is_openai_responses_compact_format, normalize_api_format_alias},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -28,10 +28,8 @@ pub enum SyncCliResponseConversionKind {
|
||||
const NON_COMPACT_STANDARD_CANDIDATE_API_FORMATS: &[&str] = &[
|
||||
"openai:chat",
|
||||
"openai:responses",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"gemini:chat",
|
||||
"gemini:cli",
|
||||
"claude:messages",
|
||||
"gemini:generate_content",
|
||||
];
|
||||
const STANDARD_API_FAMILY_ORDER: &[&str] = &["openai", "claude", "gemini"];
|
||||
|
||||
@@ -39,8 +37,8 @@ pub fn request_candidate_api_format_preference(
|
||||
client_api_format: &str,
|
||||
provider_api_format: &str,
|
||||
) -> Option<(u8, u8)> {
|
||||
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_api_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_api_format_alias(provider_api_format);
|
||||
|
||||
if client_api_format == "openai:responses:compact" {
|
||||
return (provider_api_format == "openai:responses:compact").then_some((0, 0));
|
||||
@@ -70,7 +68,7 @@ pub fn request_candidate_api_formats(
|
||||
client_api_format: &str,
|
||||
_require_streaming: bool,
|
||||
) -> Vec<&'static str> {
|
||||
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
|
||||
let client_api_format = normalize_api_format_alias(client_api_format);
|
||||
if client_api_format == "openai:responses:compact" {
|
||||
return vec!["openai:responses:compact"];
|
||||
}
|
||||
@@ -90,8 +88,8 @@ pub fn request_conversion_kind(
|
||||
client_api_format: &str,
|
||||
provider_api_format: &str,
|
||||
) -> Option<RequestConversionKind> {
|
||||
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_api_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_api_format_alias(provider_api_format);
|
||||
if client_api_format == provider_api_format {
|
||||
return None;
|
||||
}
|
||||
@@ -109,8 +107,8 @@ pub fn request_conversion_kind(
|
||||
match provider_api_format.as_str() {
|
||||
"openai:chat" => Some(RequestConversionKind::ToOpenAIChat),
|
||||
"openai:responses" => Some(RequestConversionKind::ToOpenAiResponses),
|
||||
"claude:chat" | "claude:cli" => Some(RequestConversionKind::ToClaudeStandard),
|
||||
"gemini:chat" | "gemini:cli" => Some(RequestConversionKind::ToGeminiStandard),
|
||||
"claude:messages" => Some(RequestConversionKind::ToClaudeStandard),
|
||||
"gemini:generate_content" => Some(RequestConversionKind::ToGeminiStandard),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -119,8 +117,8 @@ pub fn sync_chat_response_conversion_kind(
|
||||
provider_api_format: &str,
|
||||
client_api_format: &str,
|
||||
) -> Option<SyncChatResponseConversionKind> {
|
||||
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_api_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_api_format_alias(client_api_format);
|
||||
if provider_api_format == client_api_format {
|
||||
return None;
|
||||
}
|
||||
@@ -130,8 +128,8 @@ pub fn sync_chat_response_conversion_kind(
|
||||
request_conversion_kind(client_api_format.as_str(), provider_api_format.as_str())?;
|
||||
match client_api_format.as_str() {
|
||||
"openai:chat" => Some(SyncChatResponseConversionKind::ToOpenAIChat),
|
||||
"claude:chat" => Some(SyncChatResponseConversionKind::ToClaudeChat),
|
||||
"gemini:chat" => Some(SyncChatResponseConversionKind::ToGeminiChat),
|
||||
"claude:messages" => Some(SyncChatResponseConversionKind::ToClaudeChat),
|
||||
"gemini:generate_content" => Some(SyncChatResponseConversionKind::ToGeminiChat),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -140,8 +138,8 @@ pub fn sync_cli_response_conversion_kind(
|
||||
provider_api_format: &str,
|
||||
client_api_format: &str,
|
||||
) -> Option<SyncCliResponseConversionKind> {
|
||||
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_api_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_api_format_alias(client_api_format);
|
||||
if provider_api_format == client_api_format {
|
||||
return None;
|
||||
}
|
||||
@@ -155,8 +153,8 @@ pub fn sync_cli_response_conversion_kind(
|
||||
"openai:responses" | "openai:responses:compact" => {
|
||||
Some(SyncCliResponseConversionKind::ToOpenAiResponses)
|
||||
}
|
||||
"claude:cli" => Some(SyncCliResponseConversionKind::ToClaudeCli),
|
||||
"gemini:cli" => Some(SyncCliResponseConversionKind::ToGeminiCli),
|
||||
"claude:messages" => Some(SyncCliResponseConversionKind::ToClaudeCli),
|
||||
"gemini:generate_content" => Some(SyncCliResponseConversionKind::ToGeminiCli),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -165,8 +163,8 @@ pub fn request_conversion_requires_enable_flag(
|
||||
client_api_format: &str,
|
||||
provider_api_format: &str,
|
||||
) -> bool {
|
||||
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_api_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_api_format_alias(provider_api_format);
|
||||
match (
|
||||
api_data_format_id(client_api_format.as_str()),
|
||||
api_data_format_id(provider_api_format.as_str()),
|
||||
@@ -180,35 +178,31 @@ pub fn request_conversion_requires_enable_flag(
|
||||
|
||||
pub fn is_standard_api_format(api_format: &str) -> bool {
|
||||
matches!(
|
||||
normalize_legacy_openai_format_alias(api_format).as_str(),
|
||||
normalize_api_format_alias(api_format).as_str(),
|
||||
"openai:chat"
|
||||
| "openai:responses"
|
||||
| "openai:responses:compact"
|
||||
| "claude:chat"
|
||||
| "claude:cli"
|
||||
| "gemini:chat"
|
||||
| "gemini:cli"
|
||||
| "claude:messages"
|
||||
| "gemini:generate_content"
|
||||
)
|
||||
}
|
||||
|
||||
pub 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() {
|
||||
match normalize_api_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")),
|
||||
"openai:responses" => Some(("openai", "responses")),
|
||||
"claude:messages" => Some(("claude", "messages")),
|
||||
"gemini:generate_content" => Some(("gemini", "generate_content")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn api_data_format_id(api_format: &str) -> Option<&'static str> {
|
||||
match normalize_legacy_openai_format_alias(api_format).as_str() {
|
||||
"claude:chat" | "claude:cli" => Some("claude"),
|
||||
"gemini:chat" | "gemini:cli" => Some("gemini"),
|
||||
match normalize_api_format_alias(api_format).as_str() {
|
||||
"claude:messages" => Some("claude"),
|
||||
"gemini:generate_content" => Some("gemini"),
|
||||
"openai:chat" => Some("openai_chat"),
|
||||
"openai:responses" | "openai:responses:compact" => Some("openai_responses"),
|
||||
_ => None,
|
||||
@@ -216,7 +210,7 @@ pub fn api_data_format_id(api_format: &str) -> Option<&'static str> {
|
||||
}
|
||||
|
||||
pub fn normalized_same_standard_api_format(left: &str, right: &str) -> bool {
|
||||
legacy_openai_format_alias_matches(left, right)
|
||||
api_format_alias_matches(left, right)
|
||||
}
|
||||
|
||||
fn standard_api_family_priority(family: &str) -> u8 {
|
||||
@@ -239,8 +233,8 @@ mod tests {
|
||||
match provider_api_format {
|
||||
"openai:chat" => RequestConversionKind::ToOpenAIChat,
|
||||
"openai:responses" => RequestConversionKind::ToOpenAiResponses,
|
||||
"claude:chat" | "claude:cli" => RequestConversionKind::ToClaudeStandard,
|
||||
"gemini:chat" | "gemini:cli" => RequestConversionKind::ToGeminiStandard,
|
||||
"claude:messages" => RequestConversionKind::ToClaudeStandard,
|
||||
"gemini:generate_content" => RequestConversionKind::ToGeminiStandard,
|
||||
other => panic!("unexpected provider format {other}"),
|
||||
}
|
||||
}
|
||||
@@ -252,7 +246,7 @@ mod tests {
|
||||
Some(RequestConversionKind::ToOpenAiResponses)
|
||||
);
|
||||
assert_eq!(
|
||||
request_conversion_kind("openai:chat", "claude:cli"),
|
||||
request_conversion_kind("openai:chat", "claude:messages"),
|
||||
Some(RequestConversionKind::ToClaudeStandard)
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -260,11 +254,11 @@ mod tests {
|
||||
Some(RequestConversionKind::ToOpenAIChat)
|
||||
);
|
||||
assert_eq!(
|
||||
request_conversion_kind("openai:responses:compact", "gemini:cli"),
|
||||
request_conversion_kind("openai:responses:compact", "gemini:generate_content"),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_conversion_kind("gemini:cli", "openai:responses:compact"),
|
||||
request_conversion_kind("gemini:generate_content", "openai:responses:compact"),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -280,22 +274,20 @@ mod tests {
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_conversion_kind("gemini:chat", "claude:chat"),
|
||||
request_conversion_kind("gemini:generate_content", "claude:messages"),
|
||||
Some(RequestConversionKind::ToClaudeStandard)
|
||||
);
|
||||
assert_eq!(request_conversion_kind("claude:chat", "claude:cli"), None);
|
||||
assert_eq!(
|
||||
request_conversion_kind("claude:chat", "claude:cli"),
|
||||
Some(RequestConversionKind::ToClaudeStandard)
|
||||
request_conversion_kind("claude:messages", "claude:messages"),
|
||||
None
|
||||
);
|
||||
assert_eq!(request_conversion_kind("claude:chat", "claude:chat"), None);
|
||||
|
||||
let formats = [
|
||||
"openai:chat",
|
||||
"openai:responses",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"gemini:chat",
|
||||
"gemini:cli",
|
||||
"claude:messages",
|
||||
"gemini:generate_content",
|
||||
];
|
||||
for client_api_format in formats {
|
||||
for provider_api_format in formats {
|
||||
@@ -316,35 +308,35 @@ mod tests {
|
||||
#[test]
|
||||
fn sync_response_conversion_registry_supports_bidirectional_standard_matrix() {
|
||||
assert_eq!(
|
||||
sync_chat_response_conversion_kind("openai:chat", "claude:chat"),
|
||||
sync_chat_response_conversion_kind("openai:chat", "claude:messages"),
|
||||
Some(SyncChatResponseConversionKind::ToClaudeChat)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_chat_response_conversion_kind("claude:chat", "gemini:chat"),
|
||||
sync_chat_response_conversion_kind("claude:messages", "gemini:generate_content"),
|
||||
Some(SyncChatResponseConversionKind::ToGeminiChat)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_chat_response_conversion_kind("gemini:chat", "openai:chat"),
|
||||
sync_chat_response_conversion_kind("gemini:generate_content", "openai:chat"),
|
||||
Some(SyncChatResponseConversionKind::ToOpenAIChat)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("openai:responses", "gemini:cli"),
|
||||
sync_cli_response_conversion_kind("openai:responses", "gemini:generate_content"),
|
||||
Some(SyncCliResponseConversionKind::ToGeminiCli)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("claude:chat", "openai:responses"),
|
||||
sync_cli_response_conversion_kind("claude:messages", "openai:responses"),
|
||||
Some(SyncCliResponseConversionKind::ToOpenAiResponses)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("claude:cli", "openai:responses:compact"),
|
||||
sync_cli_response_conversion_kind("claude:messages", "openai:responses:compact"),
|
||||
Some(SyncCliResponseConversionKind::ToOpenAiResponses)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("openai:responses:compact", "claude:cli"),
|
||||
sync_cli_response_conversion_kind("openai:responses:compact", "claude:messages"),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("gemini:cli", "claude:cli"),
|
||||
sync_cli_response_conversion_kind("gemini:generate_content", "claude:messages"),
|
||||
Some(SyncCliResponseConversionKind::ToClaudeCli)
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -363,60 +355,53 @@ mod tests {
|
||||
request_candidate_api_formats("openai:chat", false),
|
||||
vec![
|
||||
"openai:chat",
|
||||
"claude:chat",
|
||||
"gemini:chat",
|
||||
"openai:responses",
|
||||
"claude:cli",
|
||||
"gemini:cli"
|
||||
"claude:messages",
|
||||
"gemini:generate_content"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("openai:responses", false),
|
||||
vec![
|
||||
"openai:responses",
|
||||
"claude:cli",
|
||||
"gemini:cli",
|
||||
"openai:chat",
|
||||
"claude:chat",
|
||||
"gemini:chat"
|
||||
"claude:messages",
|
||||
"gemini:generate_content"
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("openai:cli", false),
|
||||
request_candidate_api_formats("openai:responses", false)
|
||||
Vec::<&'static str>::new()
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("claude:cli", false),
|
||||
vec![
|
||||
"claude:cli",
|
||||
"openai:responses",
|
||||
"gemini:cli",
|
||||
"claude:chat",
|
||||
"openai:chat",
|
||||
"gemini:chat"
|
||||
]
|
||||
Vec::<&'static str>::new()
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("openai:compact", false),
|
||||
vec!["openai:responses:compact"]
|
||||
Vec::<&'static str>::new()
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "openai:responses"),
|
||||
Some((1, 0))
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "claude:chat"),
|
||||
Some((2, 1))
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "openai:chat"),
|
||||
Some((3, 0))
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_conversion_enable_flag_only_applies_to_real_data_format_conversions() {
|
||||
assert!(!request_conversion_requires_enable_flag(
|
||||
"claude:messages",
|
||||
"claude:messages"
|
||||
));
|
||||
assert!(request_conversion_requires_enable_flag(
|
||||
"claude:chat",
|
||||
"claude:cli"
|
||||
));
|
||||
@@ -425,8 +410,8 @@ mod tests {
|
||||
"openai:responses"
|
||||
));
|
||||
assert!(request_conversion_requires_enable_flag(
|
||||
"claude:chat",
|
||||
"gemini:chat"
|
||||
"claude:messages",
|
||||
"gemini:generate_content"
|
||||
));
|
||||
assert!(request_conversion_requires_enable_flag(
|
||||
"openai:compact",
|
||||
|
||||
@@ -147,11 +147,8 @@ mod tests {
|
||||
use crate::formats::FormatId;
|
||||
|
||||
#[test]
|
||||
fn cli_alias_routes_to_openai_responses() {
|
||||
assert_eq!(
|
||||
FormatId::parse("openai:cli"),
|
||||
Some(FormatId::OpenAiResponses)
|
||||
);
|
||||
fn openai_cli_alias_is_not_a_primary_format() {
|
||||
assert_eq!(FormatId::parse("openai:cli"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -25,7 +25,6 @@ pub fn provider_private_response_allows_sync_finalize(report_context: &Value) ->
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
provider_adaptation_allows_sync_finalize_envelope(envelope_name, provider_api_format)
|
||||
|| matches!(envelope_name, "claude:cli")
|
||||
}
|
||||
|
||||
pub fn normalize_provider_private_report_context(report_context: Option<&Value>) -> Option<Value> {
|
||||
@@ -63,7 +62,7 @@ pub fn normalize_provider_private_response_value(
|
||||
return Some(data);
|
||||
}
|
||||
let mut unwrapped = match report_context.get("envelope_name").and_then(Value::as_str) {
|
||||
Some("claude:cli") | Some(KIRO_ENVELOPE_NAME) => data,
|
||||
Some(KIRO_ENVELOPE_NAME) => data,
|
||||
Some(GEMINI_CLI_V1INTERNAL_ENVELOPE_NAME) => {
|
||||
if let Some(response) = data
|
||||
.get("response")
|
||||
@@ -345,7 +344,7 @@ mod tests {
|
||||
let report_context = json!({
|
||||
"has_envelope": true,
|
||||
"envelope_name": "antigravity:v1internal",
|
||||
"provider_api_format": "gemini:cli",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
});
|
||||
let normalized = normalize_provider_private_report_context(Some(&report_context))
|
||||
.expect("context should normalize");
|
||||
@@ -357,7 +356,7 @@ mod tests {
|
||||
fn unwraps_antigravity_sync_response_and_injects_ids() {
|
||||
let report_context = json!({
|
||||
"has_envelope": true,
|
||||
"provider_api_format": "gemini:cli",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"envelope_name": "antigravity:v1internal",
|
||||
"mapped_model": "claude-sonnet-4-5",
|
||||
});
|
||||
@@ -390,8 +389,8 @@ mod tests {
|
||||
fn unwraps_antigravity_stream_line_and_injects_ids() {
|
||||
let report_context = json!({
|
||||
"has_envelope": true,
|
||||
"provider_api_format": "gemini:cli",
|
||||
"client_api_format": "gemini:cli",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "gemini:generate_content",
|
||||
"envelope_name": "antigravity:v1internal",
|
||||
"mapped_model": "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ const PROVIDER_ADAPTATION_SURFACES: &[ProviderAdaptationDescriptor] = &[
|
||||
surface: ProviderAdaptationSurface::AntigravityGeminiChat,
|
||||
provider_type: Some(ANTIGRAVITY_PROVIDER_TYPE),
|
||||
envelope_name: ANTIGRAVITY_V1INTERNAL_ENVELOPE_NAME,
|
||||
anchor_api_format: "gemini:chat",
|
||||
anchor_api_format: "gemini:generate_content",
|
||||
supports_request_bridge: true,
|
||||
supports_sync_finalize_bridge: true,
|
||||
supports_stream_bridge: true,
|
||||
@@ -41,7 +41,7 @@ const PROVIDER_ADAPTATION_SURFACES: &[ProviderAdaptationDescriptor] = &[
|
||||
surface: ProviderAdaptationSurface::AntigravityGeminiCli,
|
||||
provider_type: Some(ANTIGRAVITY_PROVIDER_TYPE),
|
||||
envelope_name: ANTIGRAVITY_V1INTERNAL_ENVELOPE_NAME,
|
||||
anchor_api_format: "gemini:cli",
|
||||
anchor_api_format: "gemini:generate_content",
|
||||
supports_request_bridge: true,
|
||||
supports_sync_finalize_bridge: true,
|
||||
supports_stream_bridge: true,
|
||||
@@ -52,7 +52,7 @@ const PROVIDER_ADAPTATION_SURFACES: &[ProviderAdaptationDescriptor] = &[
|
||||
surface: ProviderAdaptationSurface::GeminiCliV1Internal,
|
||||
provider_type: None,
|
||||
envelope_name: GEMINI_CLI_V1INTERNAL_ENVELOPE_NAME,
|
||||
anchor_api_format: "gemini:cli",
|
||||
anchor_api_format: "gemini:generate_content",
|
||||
supports_request_bridge: false,
|
||||
supports_sync_finalize_bridge: true,
|
||||
supports_stream_bridge: true,
|
||||
@@ -63,7 +63,7 @@ const PROVIDER_ADAPTATION_SURFACES: &[ProviderAdaptationDescriptor] = &[
|
||||
surface: ProviderAdaptationSurface::KiroClaudeCli,
|
||||
provider_type: Some(KIRO_PROVIDER_TYPE),
|
||||
envelope_name: KIRO_ENVELOPE_NAME,
|
||||
anchor_api_format: "claude:cli",
|
||||
anchor_api_format: "claude:messages",
|
||||
supports_request_bridge: true,
|
||||
supports_sync_finalize_bridge: true,
|
||||
supports_stream_bridge: true,
|
||||
@@ -149,20 +149,20 @@ mod tests {
|
||||
assert_eq!(
|
||||
provider_adaptation_anchor_api_format(
|
||||
ANTIGRAVITY_V1INTERNAL_ENVELOPE_NAME,
|
||||
"gemini:cli"
|
||||
"gemini:generate_content"
|
||||
),
|
||||
Some("gemini:cli")
|
||||
Some("gemini:generate_content")
|
||||
);
|
||||
assert_eq!(
|
||||
provider_adaptation_anchor_api_format(
|
||||
GEMINI_CLI_V1INTERNAL_ENVELOPE_NAME,
|
||||
"gemini:cli"
|
||||
"gemini:generate_content"
|
||||
),
|
||||
Some("gemini:cli")
|
||||
Some("gemini:generate_content")
|
||||
);
|
||||
assert_eq!(
|
||||
provider_adaptation_anchor_api_format(KIRO_ENVELOPE_NAME, "claude:cli"),
|
||||
Some("claude:cli")
|
||||
provider_adaptation_anchor_api_format(KIRO_ENVELOPE_NAME, "claude:messages"),
|
||||
Some("claude:messages")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -170,19 +170,19 @@ mod tests {
|
||||
fn exposes_private_surface_capabilities() {
|
||||
assert!(provider_adaptation_allows_sync_finalize_envelope(
|
||||
ANTIGRAVITY_V1INTERNAL_ENVELOPE_NAME,
|
||||
"gemini:chat"
|
||||
"gemini:generate_content"
|
||||
));
|
||||
assert!(provider_adaptation_should_unwrap_stream_envelope(
|
||||
GEMINI_CLI_V1INTERNAL_ENVELOPE_NAME,
|
||||
"gemini:cli"
|
||||
"gemini:generate_content"
|
||||
));
|
||||
assert!(provider_adaptation_requires_eventstream_accept(
|
||||
Some(KIRO_ENVELOPE_NAME),
|
||||
"claude:cli"
|
||||
"claude:messages"
|
||||
));
|
||||
assert!(!provider_adaptation_requires_eventstream_accept(
|
||||
Some(ANTIGRAVITY_V1INTERNAL_ENVELOPE_NAME),
|
||||
"gemini:cli"
|
||||
"gemini:generate_content"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,6 @@ pub use crate::planner::standard::{
|
||||
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,
|
||||
api_format_alias_matches, api_format_storage_aliases, is_openai_responses_compact_format,
|
||||
is_openai_responses_family_format, is_openai_responses_format, normalize_api_format_alias,
|
||||
};
|
||||
|
||||
@@ -67,15 +67,15 @@ pub fn implicit_sync_finalize_report_kind(plan_kind: &str) -> Option<&'static st
|
||||
pub fn core_error_default_client_api_format(report_kind: &str) -> Option<&'static str> {
|
||||
match report_kind {
|
||||
OPENAI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some("openai:chat"),
|
||||
CLAUDE_CHAT_SYNC_FINALIZE_REPORT_KIND => Some("claude:chat"),
|
||||
GEMINI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some("gemini:chat"),
|
||||
CLAUDE_CHAT_SYNC_FINALIZE_REPORT_KIND => Some("claude:messages"),
|
||||
GEMINI_CHAT_SYNC_FINALIZE_REPORT_KIND => Some("gemini:generate_content"),
|
||||
OPENAI_RESPONSES_SYNC_FINALIZE_REPORT_KIND => Some("openai:responses"),
|
||||
OPENAI_RESPONSES_COMPACT_SYNC_FINALIZE_REPORT_KIND => Some("openai:responses:compact"),
|
||||
LEGACY_OPENAI_CLI_SYNC_FINALIZE_REPORT_KIND => Some("openai:responses"),
|
||||
LEGACY_OPENAI_COMPACT_SYNC_FINALIZE_REPORT_KIND => Some("openai:responses:compact"),
|
||||
OPENAI_IMAGE_SYNC_FINALIZE_REPORT_KIND => Some("openai:image"),
|
||||
CLAUDE_CLI_SYNC_FINALIZE_REPORT_KIND => Some("claude:cli"),
|
||||
GEMINI_CLI_SYNC_FINALIZE_REPORT_KIND => Some("gemini:cli"),
|
||||
CLAUDE_CLI_SYNC_FINALIZE_REPORT_KIND => Some("claude:messages"),
|
||||
GEMINI_CLI_SYNC_FINALIZE_REPORT_KIND => Some("gemini:generate_content"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ 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 aether_ai_formats::normalize_legacy_openai_format_alias(client_api_format).as_str() {
|
||||
match aether_ai_formats::normalize_api_format_alias(client_api_format).as_str() {
|
||||
"openai:chat" | "openai:responses" | "openai:responses:compact" => {
|
||||
error_object.insert(
|
||||
"type".to_string(),
|
||||
@@ -51,7 +51,7 @@ pub fn build_core_error_body_for_client_format(
|
||||
Value::Object(error_object),
|
||||
)])))
|
||||
}
|
||||
"claude:chat" | "claude:cli" => {
|
||||
"claude:messages" => {
|
||||
error_object.insert(
|
||||
"type".to_string(),
|
||||
Value::String(map_local_sync_error_kind_to_claude_type(kind).to_string()),
|
||||
@@ -64,7 +64,7 @@ pub fn build_core_error_body_for_client_format(
|
||||
("error".to_string(), Value::Object(error_object)),
|
||||
])))
|
||||
}
|
||||
"gemini:chat" | "gemini:cli" => Some(Value::Object(Map::from_iter([(
|
||||
"gemini:generate_content" => Some(Value::Object(Map::from_iter([(
|
||||
"error".to_string(),
|
||||
Value::Object(Map::from_iter([
|
||||
(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use aether_ai_formats::normalize_legacy_openai_format_alias;
|
||||
use aether_ai_formats::normalize_api_format_alias;
|
||||
use aether_provider_transport::auth::{
|
||||
resolve_local_gemini_auth, resolve_local_openai_bearer_auth, resolve_local_standard_auth,
|
||||
};
|
||||
@@ -80,8 +80,8 @@ pub fn request_conversion_enabled_for_transport(
|
||||
client_api_format: &str,
|
||||
provider_api_format: &str,
|
||||
) -> bool {
|
||||
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_api_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_api_format_alias(provider_api_format);
|
||||
if client_api_format == provider_api_format {
|
||||
return true;
|
||||
}
|
||||
@@ -103,8 +103,8 @@ pub fn request_pair_allowed_for_transport(
|
||||
client_api_format: &str,
|
||||
provider_api_format: &str,
|
||||
) -> bool {
|
||||
let client_api_format = normalize_legacy_openai_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_legacy_openai_format_alias(provider_api_format);
|
||||
let client_api_format = normalize_api_format_alias(client_api_format);
|
||||
let provider_api_format = normalize_api_format_alias(provider_api_format);
|
||||
if client_api_format == provider_api_format {
|
||||
return true;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ pub fn request_pair_allowed_for_transport(
|
||||
.provider_type
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("kiro")
|
||||
&& provider_api_format.eq_ignore_ascii_case("claude:cli")
|
||||
&& aether_ai_formats::api_format_alias_matches(&provider_api_format, "claude:messages")
|
||||
{
|
||||
return request_conversion_enabled_for_transport(
|
||||
transport,
|
||||
@@ -148,16 +148,15 @@ pub fn request_conversion_transport_unsupported_reason(
|
||||
.provider_type
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("kiro")
|
||||
&& transport
|
||||
.endpoint
|
||||
.api_format
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("claude:cli")
|
||||
&& aether_ai_formats::api_format_alias_matches(
|
||||
&transport.endpoint.api_format,
|
||||
"claude:messages",
|
||||
)
|
||||
{
|
||||
return local_kiro_request_transport_unsupported_reason_with_network(transport);
|
||||
}
|
||||
|
||||
match normalize_legacy_openai_format_alias(&transport.endpoint.api_format).as_str() {
|
||||
match normalize_api_format_alias(&transport.endpoint.api_format).as_str() {
|
||||
"openai:chat" => local_openai_chat_transport_unsupported_reason(transport),
|
||||
"openai:responses" | "openai:responses:compact" => {
|
||||
local_standard_transport_unsupported_reason_with_network(
|
||||
@@ -165,21 +164,16 @@ pub fn request_conversion_transport_unsupported_reason(
|
||||
transport.endpoint.api_format.trim(),
|
||||
)
|
||||
}
|
||||
"claude:chat" => {
|
||||
local_standard_transport_unsupported_reason_with_network(transport, "claude:chat")
|
||||
"claude:messages" => {
|
||||
local_standard_transport_unsupported_reason_with_network(transport, "claude:messages")
|
||||
}
|
||||
"claude:cli" => {
|
||||
local_standard_transport_unsupported_reason_with_network(transport, "claude:cli")
|
||||
}
|
||||
"gemini:chat" | "gemini:cli" if is_vertex_api_key_transport_context(transport) => {
|
||||
"gemini:generate_content" if is_vertex_api_key_transport_context(transport) => {
|
||||
local_vertex_api_key_gemini_transport_unsupported_reason_with_network(transport)
|
||||
}
|
||||
"gemini:chat" => {
|
||||
local_gemini_transport_unsupported_reason_with_network(transport, "gemini:chat")
|
||||
}
|
||||
"gemini:cli" => {
|
||||
local_gemini_transport_unsupported_reason_with_network(transport, "gemini:cli")
|
||||
}
|
||||
"gemini:generate_content" => local_gemini_transport_unsupported_reason_with_network(
|
||||
transport,
|
||||
"gemini:generate_content",
|
||||
),
|
||||
_ => Some("transport_api_format_unsupported"),
|
||||
}
|
||||
}
|
||||
@@ -188,11 +182,11 @@ pub fn request_conversion_direct_auth(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
_kind: RequestConversionKind,
|
||||
) -> Option<(String, String)> {
|
||||
match normalize_legacy_openai_format_alias(&transport.endpoint.api_format).as_str() {
|
||||
match normalize_api_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" => {
|
||||
"gemini:generate_content" => {
|
||||
if is_vertex_api_key_transport_context(transport) {
|
||||
resolve_local_vertex_api_key_query_auth(transport)
|
||||
.map(|auth| (VERTEX_API_KEY_QUERY_PARAM.to_string(), auth.value))
|
||||
@@ -200,7 +194,7 @@ pub fn request_conversion_direct_auth(
|
||||
resolve_local_gemini_auth(transport)
|
||||
}
|
||||
}
|
||||
"claude:chat" | "claude:cli" => resolve_local_standard_auth(transport),
|
||||
"claude:messages" => resolve_local_standard_auth(transport),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -243,8 +237,9 @@ fn json_format_list_contains(value: &serde_json::Value, api_format: &str) -> boo
|
||||
return false;
|
||||
};
|
||||
items.iter().any(|item| {
|
||||
item.as_str()
|
||||
.is_some_and(|candidate| candidate.trim().eq_ignore_ascii_case(api_format))
|
||||
item.as_str().is_some_and(|candidate| {
|
||||
aether_ai_formats::api_format_alias_matches(candidate, api_format)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -266,18 +261,16 @@ mod tests {
|
||||
const STANDARD_SURFACES: &[&str] = &[
|
||||
"openai:chat",
|
||||
"openai:responses",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"gemini:chat",
|
||||
"gemini:cli",
|
||||
"claude:messages",
|
||||
"gemini:generate_content",
|
||||
];
|
||||
|
||||
fn expected_request_conversion_kind(provider_api_format: &str) -> RequestConversionKind {
|
||||
match provider_api_format {
|
||||
"openai:chat" => RequestConversionKind::ToOpenAIChat,
|
||||
"openai:responses" => RequestConversionKind::ToOpenAiResponses,
|
||||
"claude:chat" | "claude:cli" => RequestConversionKind::ToClaudeStandard,
|
||||
"gemini:chat" | "gemini:cli" => RequestConversionKind::ToGeminiStandard,
|
||||
"claude:messages" => RequestConversionKind::ToClaudeStandard,
|
||||
"gemini:generate_content" => RequestConversionKind::ToGeminiStandard,
|
||||
other => panic!("unexpected provider api format: {other}"),
|
||||
}
|
||||
}
|
||||
@@ -289,7 +282,7 @@ mod tests {
|
||||
Some(RequestConversionKind::ToOpenAiResponses)
|
||||
);
|
||||
assert_eq!(
|
||||
request_conversion_kind("openai:chat", "claude:cli"),
|
||||
request_conversion_kind("openai:chat", "claude:messages"),
|
||||
Some(RequestConversionKind::ToClaudeStandard)
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -297,11 +290,11 @@ mod tests {
|
||||
Some(RequestConversionKind::ToOpenAIChat)
|
||||
);
|
||||
assert_eq!(
|
||||
request_conversion_kind("openai:responses:compact", "gemini:cli"),
|
||||
request_conversion_kind("openai:responses:compact", "gemini:generate_content"),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_conversion_kind("gemini:cli", "openai:responses:compact"),
|
||||
request_conversion_kind("gemini:generate_content", "openai:responses:compact"),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -317,14 +310,14 @@ mod tests {
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_conversion_kind("gemini:chat", "claude:chat"),
|
||||
request_conversion_kind("gemini:generate_content", "claude:messages"),
|
||||
Some(RequestConversionKind::ToClaudeStandard)
|
||||
);
|
||||
assert_eq!(request_conversion_kind("claude:chat", "claude:cli"), None);
|
||||
assert_eq!(
|
||||
request_conversion_kind("claude:chat", "claude:cli"),
|
||||
Some(RequestConversionKind::ToClaudeStandard)
|
||||
request_conversion_kind("claude:messages", "claude:messages"),
|
||||
None
|
||||
);
|
||||
assert_eq!(request_conversion_kind("claude:chat", "claude:chat"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -351,35 +344,35 @@ mod tests {
|
||||
#[test]
|
||||
fn sync_response_conversion_registry_supports_bidirectional_standard_matrix() {
|
||||
assert_eq!(
|
||||
sync_chat_response_conversion_kind("openai:chat", "claude:chat"),
|
||||
sync_chat_response_conversion_kind("openai:chat", "claude:messages"),
|
||||
Some(SyncChatResponseConversionKind::ToClaudeChat)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_chat_response_conversion_kind("claude:chat", "gemini:chat"),
|
||||
sync_chat_response_conversion_kind("claude:messages", "gemini:generate_content"),
|
||||
Some(SyncChatResponseConversionKind::ToGeminiChat)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_chat_response_conversion_kind("gemini:chat", "openai:chat"),
|
||||
sync_chat_response_conversion_kind("gemini:generate_content", "openai:chat"),
|
||||
Some(SyncChatResponseConversionKind::ToOpenAIChat)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("openai:responses", "gemini:cli"),
|
||||
sync_cli_response_conversion_kind("openai:responses", "gemini:generate_content"),
|
||||
Some(SyncCliResponseConversionKind::ToGeminiCli)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("claude:chat", "openai:responses"),
|
||||
sync_cli_response_conversion_kind("claude:messages", "openai:responses"),
|
||||
Some(SyncCliResponseConversionKind::ToOpenAiResponses)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("claude:cli", "openai:responses:compact"),
|
||||
sync_cli_response_conversion_kind("claude:messages", "openai:responses:compact"),
|
||||
Some(SyncCliResponseConversionKind::ToOpenAiResponses)
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("openai:responses:compact", "claude:cli"),
|
||||
sync_cli_response_conversion_kind("openai:responses:compact", "claude:messages"),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
sync_cli_response_conversion_kind("gemini:cli", "claude:cli"),
|
||||
sync_cli_response_conversion_kind("gemini:generate_content", "claude:messages"),
|
||||
Some(SyncCliResponseConversionKind::ToClaudeCli)
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -395,7 +388,7 @@ mod tests {
|
||||
#[test]
|
||||
fn sync_response_conversion_registry_covers_all_standard_surface_pairs() {
|
||||
for provider_api_format in STANDARD_SURFACES {
|
||||
for client_api_format in ["openai:chat", "claude:chat", "gemini:chat"] {
|
||||
for client_api_format in ["openai:chat", "claude:messages", "gemini:generate_content"] {
|
||||
let actual =
|
||||
sync_chat_response_conversion_kind(provider_api_format, client_api_format);
|
||||
if *provider_api_format == client_api_format {
|
||||
@@ -406,8 +399,8 @@ mod tests {
|
||||
} else {
|
||||
let expected = match client_api_format {
|
||||
"openai:chat" => SyncChatResponseConversionKind::ToOpenAIChat,
|
||||
"claude:chat" => SyncChatResponseConversionKind::ToClaudeChat,
|
||||
"gemini:chat" => SyncChatResponseConversionKind::ToGeminiChat,
|
||||
"claude:messages" => SyncChatResponseConversionKind::ToClaudeChat,
|
||||
"gemini:generate_content" => SyncChatResponseConversionKind::ToGeminiChat,
|
||||
other => panic!("unexpected chat client api format: {other}"),
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -418,7 +411,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
for client_api_format in ["openai:responses", "claude:cli", "gemini:cli"] {
|
||||
for client_api_format in [
|
||||
"openai:responses",
|
||||
"claude:messages",
|
||||
"gemini:generate_content",
|
||||
] {
|
||||
let actual =
|
||||
sync_cli_response_conversion_kind(provider_api_format, client_api_format);
|
||||
if *provider_api_format == client_api_format {
|
||||
@@ -429,8 +426,8 @@ mod tests {
|
||||
} else {
|
||||
let expected = match client_api_format {
|
||||
"openai:responses" => SyncCliResponseConversionKind::ToOpenAiResponses,
|
||||
"claude:cli" => SyncCliResponseConversionKind::ToClaudeCli,
|
||||
"gemini:cli" => SyncCliResponseConversionKind::ToGeminiCli,
|
||||
"claude:messages" => SyncCliResponseConversionKind::ToClaudeCli,
|
||||
"gemini:generate_content" => SyncCliResponseConversionKind::ToGeminiCli,
|
||||
other => panic!("unexpected cli client api format: {other}"),
|
||||
};
|
||||
assert_eq!(
|
||||
@@ -449,49 +446,31 @@ mod tests {
|
||||
request_candidate_api_formats("openai:chat", false),
|
||||
vec![
|
||||
"openai:chat",
|
||||
"claude:chat",
|
||||
"gemini:chat",
|
||||
"openai:responses",
|
||||
"claude:cli",
|
||||
"gemini:cli",
|
||||
"claude:messages",
|
||||
"gemini:generate_content",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("openai:responses", false),
|
||||
vec![
|
||||
"openai:responses",
|
||||
"claude:cli",
|
||||
"gemini:cli",
|
||||
"openai:chat",
|
||||
"claude:chat",
|
||||
"gemini:chat",
|
||||
"claude:messages",
|
||||
"gemini:generate_content",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("openai:cli", false),
|
||||
vec![
|
||||
"openai:responses",
|
||||
"claude:cli",
|
||||
"gemini:cli",
|
||||
"openai:chat",
|
||||
"claude:chat",
|
||||
"gemini:chat",
|
||||
]
|
||||
Vec::<&'static str>::new()
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("claude:cli", false),
|
||||
vec![
|
||||
"claude:cli",
|
||||
"openai:responses",
|
||||
"gemini:cli",
|
||||
"claude:chat",
|
||||
"openai:chat",
|
||||
"gemini:chat",
|
||||
]
|
||||
Vec::<&'static str>::new()
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_formats("openai:compact", false),
|
||||
vec!["openai:responses:compact"]
|
||||
Vec::<&'static str>::new()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -499,21 +478,25 @@ mod tests {
|
||||
fn request_candidate_registry_prefers_same_kind_before_same_family_fallbacks() {
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "openai:responses"),
|
||||
Some((1, 0))
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "claude:chat"),
|
||||
Some((2, 1))
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
request_candidate_api_format_preference("claude:cli", "openai:chat"),
|
||||
Some((3, 0))
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_conversion_enable_flag_only_applies_to_real_data_format_conversions() {
|
||||
assert!(!request_conversion_requires_enable_flag(
|
||||
"claude:messages",
|
||||
"claude:messages"
|
||||
));
|
||||
assert!(request_conversion_requires_enable_flag(
|
||||
"claude:chat",
|
||||
"claude:cli"
|
||||
));
|
||||
@@ -527,7 +510,7 @@ mod tests {
|
||||
));
|
||||
assert!(request_conversion_requires_enable_flag(
|
||||
"openai:chat",
|
||||
"gemini:chat"
|
||||
"gemini:generate_content"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -627,7 +610,7 @@ mod tests {
|
||||
config: None,
|
||||
format_acceptance_config: Some(serde_json::json!({
|
||||
"enabled": true,
|
||||
"accept_formats": ["claude:cli"],
|
||||
"accept_formats": ["claude:messages"],
|
||||
})),
|
||||
proxy: None,
|
||||
},
|
||||
@@ -652,17 +635,17 @@ mod tests {
|
||||
|
||||
assert!(request_conversion_enabled_for_transport(
|
||||
&transport,
|
||||
"claude:cli",
|
||||
"claude:messages",
|
||||
"openai:responses"
|
||||
));
|
||||
assert!(request_pair_allowed_for_transport(
|
||||
&transport,
|
||||
"claude:cli",
|
||||
"claude:messages",
|
||||
"openai:responses"
|
||||
));
|
||||
assert!(!request_pair_allowed_for_transport(
|
||||
&transport,
|
||||
"gemini:cli",
|
||||
"gemini:generate_content",
|
||||
"openai:responses"
|
||||
));
|
||||
}
|
||||
@@ -700,7 +683,7 @@ mod tests {
|
||||
config: None,
|
||||
format_acceptance_config: Some(serde_json::json!({
|
||||
"enabled": true,
|
||||
"reject_formats": ["claude:cli"],
|
||||
"reject_formats": ["claude:messages"],
|
||||
})),
|
||||
proxy: None,
|
||||
},
|
||||
@@ -725,7 +708,7 @@ mod tests {
|
||||
|
||||
assert!(!request_conversion_enabled_for_transport(
|
||||
&transport,
|
||||
"claude:cli",
|
||||
"claude:messages",
|
||||
"openai:responses"
|
||||
));
|
||||
}
|
||||
@@ -751,9 +734,9 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-vertex".to_string(),
|
||||
provider_id: "provider-vertex".to_string(),
|
||||
api_format: "gemini:chat".to_string(),
|
||||
api_format: "gemini:generate_content".to_string(),
|
||||
api_family: Some("gemini".to_string()),
|
||||
endpoint_kind: Some("chat".to_string()),
|
||||
endpoint_kind: Some("generate_content".to_string()),
|
||||
is_active: true,
|
||||
base_url: "https://aiplatform.googleapis.com".to_string(),
|
||||
header_rules: None,
|
||||
@@ -770,7 +753,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "api_key".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["gemini:chat".to_string()]),
|
||||
api_formats: Some(vec!["gemini:generate_content".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
@@ -794,7 +777,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kiro_claude_cli_transport_supports_cross_format_conversion_via_envelope() {
|
||||
fn kiro_claude_messages_transport_supports_cross_format_conversion_via_envelope() {
|
||||
let transport = GatewayProviderTransportSnapshot {
|
||||
provider: GatewayProviderTransportProvider {
|
||||
id: "provider-kiro".to_string(),
|
||||
@@ -814,9 +797,9 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-kiro".to_string(),
|
||||
provider_id: "provider-kiro".to_string(),
|
||||
api_format: "claude:cli".to_string(),
|
||||
api_format: "claude:messages".to_string(),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
endpoint_kind: Some("messages".to_string()),
|
||||
is_active: true,
|
||||
base_url: "https://q.{region}.amazonaws.com".to_string(),
|
||||
header_rules: None,
|
||||
@@ -833,7 +816,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "bearer".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["claude:cli".to_string()]),
|
||||
api_formats: Some(vec!["claude:messages".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
@@ -849,7 +832,7 @@ mod tests {
|
||||
assert!(request_pair_allowed_for_transport(
|
||||
&transport,
|
||||
"openai:chat",
|
||||
"claude:cli"
|
||||
"claude:messages"
|
||||
));
|
||||
assert!(request_conversion_transport_supported(
|
||||
&transport,
|
||||
|
||||
@@ -459,7 +459,7 @@ mod tests {
|
||||
"\"code\":\"invalid_request\"",
|
||||
),
|
||||
(
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
data_line(json!({
|
||||
"type": "error",
|
||||
"error": {
|
||||
@@ -473,7 +473,7 @@ mod tests {
|
||||
"\"code\":\"rate_limit\"",
|
||||
),
|
||||
(
|
||||
"gemini:cli",
|
||||
"gemini:generate_content",
|
||||
data_line(json!({
|
||||
"error": {
|
||||
"code": 429,
|
||||
@@ -524,7 +524,7 @@ mod tests {
|
||||
"\"code\":\"invalid_request\"",
|
||||
),
|
||||
(
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
data_line(json!({
|
||||
"type": "error",
|
||||
"error": {
|
||||
@@ -538,7 +538,7 @@ mod tests {
|
||||
"\"code\":\"rate_limit\"",
|
||||
),
|
||||
(
|
||||
"gemini:cli",
|
||||
"gemini:generate_content",
|
||||
data_line(json!({
|
||||
"error": {
|
||||
"code": 429,
|
||||
@@ -553,7 +553,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for (provider_api_format, line, message, err_type, code) in cases {
|
||||
let report_context = report_context(provider_api_format, "claude:chat");
|
||||
let report_context = report_context(provider_api_format, "claude:messages");
|
||||
let mut matrix = StreamingStandardFormatMatrix::default();
|
||||
let output = matrix
|
||||
.transform_line(&report_context, line)
|
||||
@@ -591,7 +591,7 @@ mod tests {
|
||||
"\"status\":\"INVALID_ARGUMENT\"",
|
||||
),
|
||||
(
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
data_line(json!({
|
||||
"type": "error",
|
||||
"error": {
|
||||
@@ -605,7 +605,7 @@ mod tests {
|
||||
"\"status\":\"RESOURCE_EXHAUSTED\"",
|
||||
),
|
||||
(
|
||||
"gemini:cli",
|
||||
"gemini:generate_content",
|
||||
data_line(json!({
|
||||
"error": {
|
||||
"code": 429,
|
||||
@@ -620,7 +620,7 @@ mod tests {
|
||||
];
|
||||
|
||||
for (provider_api_format, line, message, code, status) in cases {
|
||||
let report_context = report_context(provider_api_format, "gemini:chat");
|
||||
let report_context = report_context(provider_api_format, "gemini:generate_content");
|
||||
let mut matrix = StreamingStandardFormatMatrix::default();
|
||||
let output = matrix
|
||||
.transform_line(&report_context, line)
|
||||
@@ -656,7 +656,7 @@ mod tests {
|
||||
"\"code\":\"invalid_request\"",
|
||||
),
|
||||
(
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
data_line(json!({
|
||||
"type": "error",
|
||||
"error": {
|
||||
@@ -670,7 +670,7 @@ mod tests {
|
||||
"\"code\":\"rate_limit\"",
|
||||
),
|
||||
(
|
||||
"gemini:cli",
|
||||
"gemini:generate_content",
|
||||
data_line(json!({
|
||||
"error": {
|
||||
"code": 429,
|
||||
@@ -706,7 +706,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rewrites_gemini_inline_image_streams_to_claude_image_blocks() {
|
||||
let report_context = report_context("gemini:chat", "claude:chat");
|
||||
let report_context = report_context("gemini:generate_content", "claude:messages");
|
||||
let mut matrix = StreamingStandardFormatMatrix::default();
|
||||
let output = matrix
|
||||
.transform_line(
|
||||
@@ -735,7 +735,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rewrites_claude_image_blocks_to_gemini_inline_image_streams() {
|
||||
let report_context = report_context("claude:chat", "gemini:chat");
|
||||
let report_context = report_context("claude:messages", "gemini:generate_content");
|
||||
let mut matrix = StreamingStandardFormatMatrix::default();
|
||||
let output = matrix
|
||||
.transform_line(
|
||||
@@ -763,7 +763,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn terminal_observer_preserves_claude_cache_usage() {
|
||||
let report_context = report_context("claude:chat", "openai:chat");
|
||||
let report_context = report_context("claude:messages", "openai:chat");
|
||||
let mut observer = StreamingStandardTerminalObserver::default();
|
||||
|
||||
observer
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn resolve_finalize_stream_rewrite_mode(
|
||||
|
||||
if needs_conversion
|
||||
&& envelope_name.eq_ignore_ascii_case(KIRO_ENVELOPE_NAME)
|
||||
&& provider_api_format == "claude:cli"
|
||||
&& provider_api_format == "claude:messages"
|
||||
{
|
||||
return supports_standard_stream_rewrite(
|
||||
provider_api_format.as_str(),
|
||||
@@ -63,7 +63,8 @@ pub fn resolve_finalize_stream_rewrite_mode(
|
||||
}
|
||||
|
||||
if envelope_name.eq_ignore_ascii_case(KIRO_ENVELOPE_NAME) {
|
||||
return (provider_api_format == "claude:cli" && client_api_format == "claude:cli")
|
||||
return (provider_api_format == "claude:messages"
|
||||
&& client_api_format == "claude:messages")
|
||||
.then_some(FinalizeStreamRewriteMode::KiroToClaudeCli);
|
||||
}
|
||||
|
||||
@@ -83,25 +84,29 @@ fn supports_standard_stream_rewrite(provider_api_format: &str, client_api_format
|
||||
|
||||
fn is_standard_provider_api_format(api_format: &str) -> bool {
|
||||
matches!(
|
||||
aether_ai_formats::normalize_legacy_openai_format_alias(api_format).as_str(),
|
||||
aether_ai_formats::normalize_api_format_alias(api_format).as_str(),
|
||||
"openai:chat"
|
||||
| "openai:responses"
|
||||
| "openai:responses:compact"
|
||||
| "claude:chat"
|
||||
| "claude:cli"
|
||||
| "gemini:chat"
|
||||
| "gemini:cli"
|
||||
| "claude:messages"
|
||||
| "gemini:generate_content"
|
||||
)
|
||||
}
|
||||
|
||||
fn is_standard_chat_client_api_format(api_format: &str) -> bool {
|
||||
matches!(api_format, "openai:chat" | "claude:chat" | "gemini:chat")
|
||||
matches!(
|
||||
api_format,
|
||||
"openai:chat" | "claude:messages" | "gemini:generate_content"
|
||||
)
|
||||
}
|
||||
|
||||
fn is_standard_cli_client_api_format(api_format: &str) -> bool {
|
||||
matches!(
|
||||
aether_ai_formats::normalize_legacy_openai_format_alias(api_format).as_str(),
|
||||
"openai:responses" | "openai:responses:compact" | "claude:cli" | "gemini:cli"
|
||||
aether_ai_formats::normalize_api_format_alias(api_format).as_str(),
|
||||
"openai:responses"
|
||||
| "openai:responses:compact"
|
||||
| "claude:messages"
|
||||
| "gemini:generate_content"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -114,7 +119,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_standard_mode_for_cross_format_standard_streams() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "claude:chat",
|
||||
"provider_api_format": "claude:messages",
|
||||
"client_api_format": "openai:chat",
|
||||
"needs_conversion": true,
|
||||
});
|
||||
@@ -127,8 +132,8 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_envelope_unwrap_for_same_format_private_envelopes() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "gemini:cli",
|
||||
"client_api_format": "gemini:cli",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "gemini:generate_content",
|
||||
"envelope_name": "antigravity:v1internal",
|
||||
"needs_conversion": false,
|
||||
});
|
||||
@@ -141,8 +146,8 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_kiro_same_format_streams_to_kiro_mode() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "claude:cli",
|
||||
"client_api_format": "claude:cli",
|
||||
"provider_api_format": "claude:messages",
|
||||
"client_api_format": "claude:messages",
|
||||
"envelope_name": "kiro:generateAssistantResponse",
|
||||
"needs_conversion": false,
|
||||
});
|
||||
|
||||
@@ -176,10 +176,10 @@ pub fn maybe_build_openai_chat_cross_format_sync_product_from_normalized_payload
|
||||
};
|
||||
|
||||
let Some(client_body_json) = (match provider_api_format.as_str() {
|
||||
"claude:chat" | "claude:cli" => {
|
||||
"claude:messages" => {
|
||||
convert_claude_canonical_response_to_openai_chat(&provider_body_json, report_context)
|
||||
}
|
||||
"gemini:chat" | "gemini:cli" => {
|
||||
"gemini:generate_content" => {
|
||||
convert_gemini_canonical_response_to_openai_chat(&provider_body_json, report_context)
|
||||
}
|
||||
"openai:responses" => {
|
||||
@@ -248,10 +248,10 @@ pub fn maybe_build_openai_responses_cross_format_sync_product_from_normalized_pa
|
||||
normalize_openai_responses_family_api_format(&provider_api_format);
|
||||
let Some(client_body_json) = (match normalized_provider_api_format.as_str() {
|
||||
"openai:responses" | "openai:responses:compact" => Some(provider_body_json.clone()),
|
||||
"claude:chat" | "claude:cli" => {
|
||||
"claude:messages" => {
|
||||
convert_claude_response_to_openai_responses(&provider_body_json, report_context)
|
||||
}
|
||||
"gemini:chat" | "gemini:cli" => convert_gemini_canonical_response_to_openai_responses(
|
||||
"gemini:generate_content" => convert_gemini_canonical_response_to_openai_responses(
|
||||
&provider_body_json,
|
||||
report_context,
|
||||
normalized_client_api_format == "openai:responses:compact",
|
||||
@@ -541,8 +541,8 @@ fn maybe_build_openai_cross_format_provider_body_from_normalized_payload(
|
||||
let normalized_provider_api_format =
|
||||
normalize_openai_responses_family_api_format(provider_api_format);
|
||||
match normalized_provider_api_format.as_str() {
|
||||
"claude:chat" | "claude:cli" => aggregate_claude_stream_sync_response(&body_bytes),
|
||||
"gemini:chat" | "gemini:cli" => aggregate_gemini_stream_sync_response(&body_bytes),
|
||||
"claude:messages" => aggregate_claude_stream_sync_response(&body_bytes),
|
||||
"gemini:generate_content" => aggregate_gemini_stream_sync_response(&body_bytes),
|
||||
"openai:responses" | "openai:responses:compact" => {
|
||||
aggregate_openai_responses_stream_sync_response(&body_bytes)
|
||||
}
|
||||
@@ -622,13 +622,13 @@ pub fn aggregate_standard_chat_stream_sync_response(
|
||||
body: &[u8],
|
||||
provider_api_format: &str,
|
||||
) -> Option<Value> {
|
||||
match aether_ai_formats::normalize_legacy_openai_format_alias(provider_api_format).as_str() {
|
||||
match aether_ai_formats::normalize_api_format_alias(provider_api_format).as_str() {
|
||||
"openai:chat" => aggregate_openai_chat_stream_sync_response(body),
|
||||
"openai:responses" | "openai:responses:compact" => {
|
||||
aggregate_openai_responses_stream_sync_response(body)
|
||||
}
|
||||
"claude:chat" | "claude:cli" => aggregate_claude_stream_sync_response(body),
|
||||
"gemini:chat" | "gemini:cli" => aggregate_gemini_stream_sync_response(body),
|
||||
"claude:messages" => aggregate_claude_stream_sync_response(body),
|
||||
"gemini:generate_content" => aggregate_gemini_stream_sync_response(body),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -660,7 +660,7 @@ pub fn convert_standard_chat_response(
|
||||
}
|
||||
if matches!(
|
||||
provider_api_format.trim().to_ascii_lowercase().as_str(),
|
||||
"claude:chat" | "claude:cli"
|
||||
"claude:messages"
|
||||
) {
|
||||
return convert_claude_canonical_chat_response(
|
||||
body_json,
|
||||
@@ -670,7 +670,7 @@ pub fn convert_standard_chat_response(
|
||||
}
|
||||
if matches!(
|
||||
provider_api_format.trim().to_ascii_lowercase().as_str(),
|
||||
"gemini:chat" | "gemini:cli"
|
||||
"gemini:generate_content"
|
||||
) {
|
||||
return convert_gemini_canonical_chat_response(
|
||||
body_json,
|
||||
@@ -725,13 +725,13 @@ pub fn convert_standard_cli_response(
|
||||
}
|
||||
if matches!(
|
||||
provider_api_format.trim().to_ascii_lowercase().as_str(),
|
||||
"claude:chat" | "claude:cli"
|
||||
"claude:messages"
|
||||
) {
|
||||
return convert_claude_canonical_cli_response(body_json, client_api_format, report_context);
|
||||
}
|
||||
if matches!(
|
||||
provider_api_format.trim().to_ascii_lowercase().as_str(),
|
||||
"gemini:chat" | "gemini:cli"
|
||||
"gemini:generate_content"
|
||||
) {
|
||||
return convert_gemini_canonical_cli_response(body_json, client_api_format, report_context);
|
||||
}
|
||||
@@ -760,8 +760,12 @@ pub fn convert_standard_cli_response(
|
||||
"openai:responses:compact" => {
|
||||
convert_openai_chat_response_to_openai_responses(&canonical, report_context, true)
|
||||
}
|
||||
"claude:cli" => convert_openai_chat_response_to_claude_chat(&canonical, report_context),
|
||||
"gemini:cli" => convert_openai_chat_response_to_gemini_chat(&canonical, report_context),
|
||||
"claude:messages" => {
|
||||
convert_openai_chat_response_to_claude_chat(&canonical, report_context)
|
||||
}
|
||||
"gemini:generate_content" => {
|
||||
convert_openai_chat_response_to_gemini_chat(&canonical, report_context)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -787,19 +791,23 @@ fn convert_openai_chat_canonical_chat_response(
|
||||
if !openai_chat_response_can_use_single_response_canonical(body_json) {
|
||||
return match client_api_format.trim().to_ascii_lowercase().as_str() {
|
||||
"openai:chat" => Some(body_json.clone()),
|
||||
"claude:chat" => convert_openai_chat_response_to_claude_chat(body_json, report_context),
|
||||
"gemini:chat" => convert_openai_chat_response_to_gemini_chat(body_json, report_context),
|
||||
"claude:messages" => {
|
||||
convert_openai_chat_response_to_claude_chat(body_json, report_context)
|
||||
}
|
||||
"gemini:generate_content" => {
|
||||
convert_openai_chat_response_to_gemini_chat(body_json, report_context)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
match client_api_format.trim().to_ascii_lowercase().as_str() {
|
||||
"openai:chat" => Some(body_json.clone()),
|
||||
"claude:chat" => {
|
||||
"claude:messages" => {
|
||||
let openai_chat =
|
||||
convert_openai_chat_canonical_response_to_openai_chat(body_json, report_context)?;
|
||||
convert_openai_chat_response_to_claude_chat(&openai_chat, report_context)
|
||||
}
|
||||
"gemini:chat" => {
|
||||
"gemini:generate_content" => {
|
||||
let openai_chat =
|
||||
convert_openai_chat_canonical_response_to_openai_chat(body_json, report_context)?;
|
||||
convert_openai_chat_response_to_gemini_chat(&openai_chat, report_context)
|
||||
@@ -821,8 +829,12 @@ fn convert_openai_chat_canonical_cli_response(
|
||||
"openai:responses:compact" => {
|
||||
convert_openai_chat_response_to_openai_responses(body_json, report_context, true)
|
||||
}
|
||||
"claude:cli" => convert_openai_chat_response_to_claude_chat(body_json, report_context),
|
||||
"gemini:cli" => convert_openai_chat_response_to_gemini_chat(body_json, report_context),
|
||||
"claude:messages" => {
|
||||
convert_openai_chat_response_to_claude_chat(body_json, report_context)
|
||||
}
|
||||
"gemini:generate_content" => {
|
||||
convert_openai_chat_response_to_gemini_chat(body_json, report_context)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@@ -835,8 +847,12 @@ fn convert_openai_chat_canonical_cli_response(
|
||||
"openai:responses:compact" => {
|
||||
convert_openai_chat_response_to_openai_responses(&openai_chat, report_context, true)
|
||||
}
|
||||
"claude:cli" => convert_openai_chat_response_to_claude_chat(&openai_chat, report_context),
|
||||
"gemini:cli" => convert_openai_chat_response_to_gemini_chat(&openai_chat, report_context),
|
||||
"claude:messages" => {
|
||||
convert_openai_chat_response_to_claude_chat(&openai_chat, report_context)
|
||||
}
|
||||
"gemini:generate_content" => {
|
||||
convert_openai_chat_response_to_gemini_chat(&openai_chat, report_context)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -873,7 +889,7 @@ fn convert_openai_responses_canonical_chat_response(
|
||||
.or_else(|| {
|
||||
convert_openai_responses_response_to_openai_chat(body_json, report_context)
|
||||
}),
|
||||
"claude:chat" => canonical
|
||||
"claude:messages" => canonical
|
||||
.as_ref()
|
||||
.map(canonical_to_claude_response)
|
||||
.or_else(|| {
|
||||
@@ -881,7 +897,7 @@ fn convert_openai_responses_canonical_chat_response(
|
||||
convert_openai_responses_response_to_openai_chat(body_json, report_context)?;
|
||||
convert_openai_chat_response_to_claude_chat(&openai_chat, report_context)
|
||||
}),
|
||||
"gemini:chat" => canonical
|
||||
"gemini:generate_content" => canonical
|
||||
.as_ref()
|
||||
.and_then(|canonical| canonical_to_gemini_response(canonical, report_context))
|
||||
.or_else(|| {
|
||||
@@ -921,7 +937,7 @@ fn convert_openai_responses_canonical_responses_response(
|
||||
convert_openai_chat_response_to_openai_responses(&openai_chat, report_context, true)
|
||||
})
|
||||
}
|
||||
"claude:cli" => {
|
||||
"claude:messages" => {
|
||||
let canonical =
|
||||
from_openai_responses_to_canonical_response(body_json).map(|mut canonical| {
|
||||
apply_report_context_model_fallback(&mut canonical.model, report_context);
|
||||
@@ -938,7 +954,7 @@ fn convert_openai_responses_canonical_responses_response(
|
||||
convert_openai_chat_response_to_claude_chat(&openai_chat, report_context)
|
||||
})
|
||||
}
|
||||
"gemini:cli" => {
|
||||
"gemini:generate_content" => {
|
||||
let canonical =
|
||||
from_openai_responses_to_canonical_response(body_json).map(|mut canonical| {
|
||||
apply_report_context_model_fallback(&mut canonical.model, report_context);
|
||||
@@ -982,8 +998,8 @@ fn convert_claude_canonical_chat_response(
|
||||
"openai:chat" => {
|
||||
convert_claude_canonical_response_to_openai_chat(body_json, report_context)
|
||||
}
|
||||
"claude:chat" => convert_claude_canonical_response_to_claude(body_json, report_context),
|
||||
"gemini:chat" => {
|
||||
"claude:messages" => convert_claude_canonical_response_to_claude(body_json, report_context),
|
||||
"gemini:generate_content" => {
|
||||
let openai_chat =
|
||||
convert_claude_canonical_response_to_openai_chat(body_json, report_context)?;
|
||||
convert_openai_chat_response_to_gemini_chat(&openai_chat, report_context)
|
||||
@@ -1008,8 +1024,8 @@ fn convert_claude_canonical_cli_response(
|
||||
convert_claude_canonical_response_to_openai_chat(body_json, report_context)?;
|
||||
convert_openai_chat_response_to_openai_responses(&openai_chat, report_context, true)
|
||||
}
|
||||
"claude:cli" => convert_claude_canonical_response_to_claude(body_json, report_context),
|
||||
"gemini:cli" => {
|
||||
"claude:messages" => convert_claude_canonical_response_to_claude(body_json, report_context),
|
||||
"gemini:generate_content" => {
|
||||
let openai_chat =
|
||||
convert_claude_canonical_response_to_openai_chat(body_json, report_context)?;
|
||||
convert_openai_chat_response_to_gemini_chat(&openai_chat, report_context)
|
||||
@@ -1054,10 +1070,10 @@ fn convert_gemini_canonical_chat_response(
|
||||
let openai_chat = convert_gemini_chat_response_to_openai_chat(body_json, report_context)?;
|
||||
return match client_api_format.trim().to_ascii_lowercase().as_str() {
|
||||
"openai:chat" => Some(openai_chat),
|
||||
"claude:chat" => {
|
||||
"claude:messages" => {
|
||||
convert_openai_chat_response_to_claude_chat(&openai_chat, report_context)
|
||||
}
|
||||
"gemini:chat" => {
|
||||
"gemini:generate_content" => {
|
||||
convert_openai_chat_response_to_gemini_chat(&openai_chat, report_context)
|
||||
}
|
||||
_ => None,
|
||||
@@ -1067,12 +1083,14 @@ fn convert_gemini_canonical_chat_response(
|
||||
"openai:chat" => {
|
||||
convert_gemini_canonical_response_to_openai_chat(body_json, report_context)
|
||||
}
|
||||
"claude:chat" => {
|
||||
"claude:messages" => {
|
||||
let openai_chat =
|
||||
convert_gemini_canonical_response_to_openai_chat(body_json, report_context)?;
|
||||
convert_openai_chat_response_to_claude_chat(&openai_chat, report_context)
|
||||
}
|
||||
"gemini:chat" => convert_gemini_canonical_response_to_gemini(body_json, report_context),
|
||||
"gemini:generate_content" => {
|
||||
convert_gemini_canonical_response_to_gemini(body_json, report_context)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1093,10 +1111,10 @@ fn convert_gemini_canonical_cli_response(
|
||||
"openai:responses:compact" => {
|
||||
convert_openai_chat_response_to_openai_responses(&openai_chat, report_context, true)
|
||||
}
|
||||
"claude:cli" => {
|
||||
"claude:messages" => {
|
||||
convert_openai_chat_response_to_claude_chat(&openai_chat, report_context)
|
||||
}
|
||||
"gemini:cli" => {
|
||||
"gemini:generate_content" => {
|
||||
convert_openai_chat_response_to_gemini_chat(&openai_chat, report_context)
|
||||
}
|
||||
_ => None,
|
||||
@@ -1109,12 +1127,14 @@ fn convert_gemini_canonical_cli_response(
|
||||
"openai:responses:compact" => {
|
||||
convert_gemini_canonical_response_to_openai_responses(body_json, report_context, true)
|
||||
}
|
||||
"claude:cli" => {
|
||||
"claude:messages" => {
|
||||
let openai_chat =
|
||||
convert_gemini_canonical_response_to_openai_chat(body_json, report_context)?;
|
||||
convert_openai_chat_response_to_claude_chat(&openai_chat, report_context)
|
||||
}
|
||||
"gemini:cli" => convert_gemini_canonical_response_to_gemini(body_json, report_context),
|
||||
"gemini:generate_content" => {
|
||||
convert_gemini_canonical_response_to_gemini(body_json, report_context)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1230,10 +1250,10 @@ fn is_openai_responses_finalize_kind(report_kind: &str) -> bool {
|
||||
fn standard_same_format_api_format(report_kind: &str) -> Option<&'static str> {
|
||||
match report_kind {
|
||||
"openai_chat_sync_finalize" => Some("openai:chat"),
|
||||
"claude_chat_sync_finalize" => Some("claude:chat"),
|
||||
"gemini_chat_sync_finalize" => Some("gemini:chat"),
|
||||
"claude_cli_sync_finalize" => Some("claude:cli"),
|
||||
"gemini_cli_sync_finalize" => Some("gemini:cli"),
|
||||
"claude_chat_sync_finalize" => Some("claude:messages"),
|
||||
"gemini_chat_sync_finalize" => Some("gemini:generate_content"),
|
||||
"claude_cli_sync_finalize" => Some("claude:messages"),
|
||||
"gemini_cli_sync_finalize" => Some("gemini:generate_content"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1241,8 +1261,8 @@ fn standard_same_format_api_format(report_kind: &str) -> Option<&'static str> {
|
||||
fn aggregate_same_format_stream_sync_response(api_format: &str, body: &[u8]) -> Option<Value> {
|
||||
match api_format {
|
||||
"openai:chat" => aggregate_openai_chat_stream_sync_response(body),
|
||||
"claude:chat" | "claude:cli" => aggregate_claude_stream_sync_response(body),
|
||||
"gemini:chat" | "gemini:cli" => aggregate_gemini_stream_sync_response(body),
|
||||
"claude:messages" => aggregate_claude_stream_sync_response(body),
|
||||
"gemini:generate_content" => aggregate_gemini_stream_sync_response(body),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1255,7 +1275,7 @@ fn is_openai_responses_family_api_format(api_format: &str) -> bool {
|
||||
}
|
||||
|
||||
fn normalize_openai_responses_family_api_format(api_format: &str) -> String {
|
||||
aether_ai_formats::normalize_legacy_openai_format_alias(api_format)
|
||||
aether_ai_formats::normalize_api_format_alias(api_format)
|
||||
}
|
||||
|
||||
fn parse_stream_json_events(body: &[u8]) -> Option<Vec<Value>> {
|
||||
@@ -2945,7 +2965,7 @@ mod tests {
|
||||
"data: {\"responseId\":\"resp_gem_stream_456\",\"modelVersion\":\"gemini-2.5-pro\",\"candidates\":[{\"index\":0,\"content\":{\"role\":\"model\",\"parts\":[]},\"finishReason\":\"STOP\"}],\"usageMetadata\":{\"promptTokenCount\":4,\"candidatesTokenCount\":2,\"totalTokenCount\":6}}\n\n",
|
||||
);
|
||||
let report_context = json!({
|
||||
"provider_api_format": "gemini:chat",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "openai:chat",
|
||||
"mapped_model": "gemini-2.5-pro",
|
||||
});
|
||||
@@ -2992,7 +3012,7 @@ mod tests {
|
||||
);
|
||||
let report_context = json!({
|
||||
"provider_api_format": "openai:chat",
|
||||
"client_api_format": "claude:chat",
|
||||
"client_api_format": "claude:messages",
|
||||
});
|
||||
|
||||
let product = maybe_build_standard_cross_format_sync_product_from_normalized_payload(
|
||||
@@ -3049,7 +3069,7 @@ mod tests {
|
||||
fn falls_back_to_body_json_when_stream_aggregation_returns_none() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "openai:chat",
|
||||
"client_api_format": "claude:chat",
|
||||
"client_api_format": "claude:messages",
|
||||
});
|
||||
let provider_body_json = json!({
|
||||
"id": "chatcmpl_123",
|
||||
@@ -3103,8 +3123,8 @@ mod tests {
|
||||
"data: {\"type\":\"message_stop\"}\n\n",
|
||||
);
|
||||
let report_context = json!({
|
||||
"provider_api_format": "claude:chat",
|
||||
"client_api_format": "claude:chat",
|
||||
"provider_api_format": "claude:messages",
|
||||
"client_api_format": "claude:messages",
|
||||
"needs_conversion": false,
|
||||
});
|
||||
|
||||
@@ -3129,8 +3149,8 @@ mod tests {
|
||||
#[test]
|
||||
fn falls_back_to_body_json_for_standard_same_format_sync_payload() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "gemini:cli",
|
||||
"client_api_format": "gemini:cli",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "gemini:generate_content",
|
||||
"needs_conversion": false,
|
||||
});
|
||||
let provider_body_json = json!({
|
||||
@@ -3181,8 +3201,8 @@ mod tests {
|
||||
#[test]
|
||||
fn rejects_standard_same_format_error_body_json() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "claude:chat",
|
||||
"client_api_format": "claude:chat",
|
||||
"provider_api_format": "claude:messages",
|
||||
"client_api_format": "claude:messages",
|
||||
"needs_conversion": false,
|
||||
});
|
||||
let provider_body_json = json!({
|
||||
@@ -3476,7 +3496,7 @@ mod tests {
|
||||
#[test]
|
||||
fn builds_openai_chat_cross_format_sync_product_from_claude_body_json() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "claude:chat",
|
||||
"provider_api_format": "claude:messages",
|
||||
"client_api_format": "openai:chat",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "claude-sonnet-4",
|
||||
@@ -3514,7 +3534,7 @@ mod tests {
|
||||
#[test]
|
||||
fn claude_canonical_response_route_matches_legacy_outputs_for_existing_clients() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "claude:chat",
|
||||
"provider_api_format": "claude:messages",
|
||||
"client_api_format": "openai:chat",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "claude-sonnet-4",
|
||||
@@ -3551,7 +3571,7 @@ mod tests {
|
||||
.expect("legacy claude -> openai chat");
|
||||
let converted_openai = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"openai:chat",
|
||||
&report_context,
|
||||
)
|
||||
@@ -3563,8 +3583,8 @@ mod tests {
|
||||
.expect("legacy claude -> openai chat -> claude");
|
||||
let converted_claude = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"claude:chat",
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"claude:messages",
|
||||
&report_context,
|
||||
)
|
||||
.expect("canonical claude -> claude");
|
||||
@@ -3578,7 +3598,7 @@ mod tests {
|
||||
.expect("legacy claude -> openai responses");
|
||||
let converted_openai_responses = convert_standard_cli_response(
|
||||
&provider_body_json,
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"openai:responses",
|
||||
&report_context,
|
||||
)
|
||||
@@ -3589,7 +3609,7 @@ mod tests {
|
||||
#[test]
|
||||
fn gemini_canonical_response_route_matches_legacy_outputs_for_single_candidate() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "gemini:chat",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "openai:chat",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "gemini-2.5-pro",
|
||||
@@ -3628,7 +3648,7 @@ mod tests {
|
||||
.expect("legacy gemini -> openai chat");
|
||||
let converted_openai = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"openai:chat",
|
||||
&report_context,
|
||||
)
|
||||
@@ -3640,8 +3660,8 @@ mod tests {
|
||||
.expect("legacy gemini -> openai chat -> claude");
|
||||
let converted_claude = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"gemini:chat",
|
||||
"claude:chat",
|
||||
"gemini:generate_content",
|
||||
"claude:messages",
|
||||
&report_context,
|
||||
)
|
||||
.expect("canonical gemini -> claude");
|
||||
@@ -3655,7 +3675,7 @@ mod tests {
|
||||
.expect("legacy gemini -> openai responses");
|
||||
let converted_openai_responses = convert_standard_cli_response(
|
||||
&provider_body_json,
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"openai:responses",
|
||||
&report_context,
|
||||
)
|
||||
@@ -3667,7 +3687,7 @@ mod tests {
|
||||
fn openai_chat_canonical_response_route_matches_legacy_outputs_for_single_choice() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "openai:chat",
|
||||
"client_api_format": "claude:chat",
|
||||
"client_api_format": "claude:messages",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "gpt-5",
|
||||
});
|
||||
@@ -3712,7 +3732,7 @@ mod tests {
|
||||
let converted_claude = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"openai:chat",
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
&report_context,
|
||||
)
|
||||
.expect("canonical openai chat -> claude");
|
||||
@@ -3724,7 +3744,7 @@ mod tests {
|
||||
let converted_gemini = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"openai:chat",
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
&report_context,
|
||||
)
|
||||
.expect("canonical openai chat -> gemini");
|
||||
@@ -3818,7 +3838,7 @@ mod tests {
|
||||
let converted_claude = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"openai:responses",
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
&report_context,
|
||||
)
|
||||
.expect("canonical openai responses -> claude");
|
||||
@@ -3832,7 +3852,7 @@ mod tests {
|
||||
let converted_gemini = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"openai:responses",
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
&report_context,
|
||||
)
|
||||
.expect("canonical openai responses -> gemini");
|
||||
@@ -3866,7 +3886,7 @@ mod tests {
|
||||
#[test]
|
||||
fn builds_openai_chat_cross_format_sync_product_from_gemini_body_json() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "gemini:chat",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "openai:chat",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "gemini-2.5-pro",
|
||||
@@ -3957,7 +3977,7 @@ mod tests {
|
||||
"data: {\"responseId\":\"resp_cli_stream_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Hello Gemini CLI\"}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"gemini-2.5-pro-upstream\",\"usageMetadata\":{\"promptTokenCount\":2,\"candidatesTokenCount\":3,\"totalTokenCount\":5}}\n\n",
|
||||
);
|
||||
let report_context = json!({
|
||||
"provider_api_format": "gemini:cli",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "openai:responses",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "gemini-2.5-pro-upstream",
|
||||
@@ -4155,7 +4175,7 @@ mod tests {
|
||||
#[test]
|
||||
fn standard_sync_finalize_product_handles_openai_chat_cross_format() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "claude:chat",
|
||||
"provider_api_format": "claude:messages",
|
||||
"client_api_format": "openai:chat",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "claude-sonnet-4",
|
||||
@@ -4192,7 +4212,7 @@ mod tests {
|
||||
"data: {\"responseId\":\"resp_cli_stream_123\",\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"Hello Gemini CLI\"}],\"role\":\"model\"},\"finishReason\":\"STOP\",\"index\":0}],\"modelVersion\":\"gemini-2.5-pro-upstream\",\"usageMetadata\":{\"promptTokenCount\":2,\"candidatesTokenCount\":3,\"totalTokenCount\":5}}\n\n",
|
||||
);
|
||||
let report_context = json!({
|
||||
"provider_api_format": "gemini:cli",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "openai:responses",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "gemini-2.5-pro-upstream",
|
||||
@@ -4218,7 +4238,7 @@ mod tests {
|
||||
fn standard_sync_finalize_product_falls_back_to_generic_standard_cross_format() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "openai:chat",
|
||||
"client_api_format": "claude:chat",
|
||||
"client_api_format": "claude:messages",
|
||||
});
|
||||
let provider_body_json = json!({
|
||||
"id": "chatcmpl_123",
|
||||
@@ -4255,7 +4275,7 @@ mod tests {
|
||||
fn multi_choice_openai_chat_response_keeps_legacy_fallback_route() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "openai:chat",
|
||||
"client_api_format": "claude:chat",
|
||||
"client_api_format": "claude:messages",
|
||||
"model": "gpt-5",
|
||||
"mapped_model": "gpt-5-upstream",
|
||||
});
|
||||
@@ -4288,7 +4308,7 @@ mod tests {
|
||||
let converted = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"openai:chat",
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
&report_context,
|
||||
)
|
||||
.expect("fallback route");
|
||||
@@ -4298,7 +4318,7 @@ mod tests {
|
||||
#[test]
|
||||
fn multi_candidate_gemini_response_keeps_legacy_fallback_route() {
|
||||
let report_context = json!({
|
||||
"provider_api_format": "gemini:chat",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"client_api_format": "openai:chat",
|
||||
"model": "gemini-2.5-pro",
|
||||
"mapped_model": "gemini-upstream",
|
||||
@@ -4330,7 +4350,7 @@ mod tests {
|
||||
.expect("legacy multi candidate route");
|
||||
let converted = convert_standard_chat_response(
|
||||
&provider_body_json,
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"openai:chat",
|
||||
&report_context,
|
||||
)
|
||||
|
||||
@@ -60,18 +60,21 @@ mod tests {
|
||||
"codex",
|
||||
"openai:responses"
|
||||
));
|
||||
assert!(force_upstream_streaming_for_provider("codex", "openai:cli"));
|
||||
assert!(!force_upstream_streaming_for_provider(
|
||||
"codex",
|
||||
"openai:responses:compact"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_force_streaming_for_compact_or_other_provider_types() {
|
||||
assert!(!force_upstream_streaming_for_provider(
|
||||
"codex",
|
||||
"openai:compact"
|
||||
"openai:responses:compact"
|
||||
));
|
||||
assert!(!force_upstream_streaming_for_provider(
|
||||
"openai",
|
||||
"openai:cli"
|
||||
"openai:responses"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,28 +22,28 @@ pub struct LocalSameFormatProviderSpec {
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec> {
|
||||
match plan_kind {
|
||||
CLAUDE_CHAT_SYNC_PLAN_KIND => Some(LocalSameFormatProviderSpec {
|
||||
api_format: "claude:chat",
|
||||
api_format: "claude:messages",
|
||||
decision_kind: CLAUDE_CHAT_SYNC_PLAN_KIND,
|
||||
report_kind: "claude_chat_sync_success",
|
||||
family: LocalSameFormatProviderFamily::Standard,
|
||||
require_streaming: false,
|
||||
}),
|
||||
CLAUDE_CLI_SYNC_PLAN_KIND => Some(LocalSameFormatProviderSpec {
|
||||
api_format: "claude:cli",
|
||||
api_format: "claude:messages",
|
||||
decision_kind: CLAUDE_CLI_SYNC_PLAN_KIND,
|
||||
report_kind: "claude_cli_sync_success",
|
||||
family: LocalSameFormatProviderFamily::Standard,
|
||||
require_streaming: false,
|
||||
}),
|
||||
GEMINI_CHAT_SYNC_PLAN_KIND => Some(LocalSameFormatProviderSpec {
|
||||
api_format: "gemini:chat",
|
||||
api_format: "gemini:generate_content",
|
||||
decision_kind: GEMINI_CHAT_SYNC_PLAN_KIND,
|
||||
report_kind: "gemini_chat_sync_success",
|
||||
family: LocalSameFormatProviderFamily::Gemini,
|
||||
require_streaming: false,
|
||||
}),
|
||||
GEMINI_CLI_SYNC_PLAN_KIND => Some(LocalSameFormatProviderSpec {
|
||||
api_format: "gemini:cli",
|
||||
api_format: "gemini:generate_content",
|
||||
decision_kind: GEMINI_CLI_SYNC_PLAN_KIND,
|
||||
report_kind: "gemini_cli_sync_success",
|
||||
family: LocalSameFormatProviderFamily::Gemini,
|
||||
@@ -56,28 +56,28 @@ pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec>
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec> {
|
||||
match plan_kind {
|
||||
CLAUDE_CHAT_STREAM_PLAN_KIND => Some(LocalSameFormatProviderSpec {
|
||||
api_format: "claude:chat",
|
||||
api_format: "claude:messages",
|
||||
decision_kind: CLAUDE_CHAT_STREAM_PLAN_KIND,
|
||||
report_kind: "claude_chat_stream_success",
|
||||
family: LocalSameFormatProviderFamily::Standard,
|
||||
require_streaming: true,
|
||||
}),
|
||||
CLAUDE_CLI_STREAM_PLAN_KIND => Some(LocalSameFormatProviderSpec {
|
||||
api_format: "claude:cli",
|
||||
api_format: "claude:messages",
|
||||
decision_kind: CLAUDE_CLI_STREAM_PLAN_KIND,
|
||||
report_kind: "claude_cli_stream_success",
|
||||
family: LocalSameFormatProviderFamily::Standard,
|
||||
require_streaming: true,
|
||||
}),
|
||||
GEMINI_CHAT_STREAM_PLAN_KIND => Some(LocalSameFormatProviderSpec {
|
||||
api_format: "gemini:chat",
|
||||
api_format: "gemini:generate_content",
|
||||
decision_kind: GEMINI_CHAT_STREAM_PLAN_KIND,
|
||||
report_kind: "gemini_chat_stream_success",
|
||||
family: LocalSameFormatProviderFamily::Gemini,
|
||||
require_streaming: true,
|
||||
}),
|
||||
GEMINI_CLI_STREAM_PLAN_KIND => Some(LocalSameFormatProviderSpec {
|
||||
api_format: "gemini:cli",
|
||||
api_format: "gemini:generate_content",
|
||||
decision_kind: GEMINI_CLI_STREAM_PLAN_KIND,
|
||||
report_kind: "gemini_cli_stream_success",
|
||||
family: LocalSameFormatProviderFamily::Gemini,
|
||||
@@ -94,7 +94,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_claude_sync_same_format_spec() {
|
||||
let spec = resolve_sync_spec("claude_chat_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "claude:chat");
|
||||
assert_eq!(spec.api_format, "claude:messages");
|
||||
assert_eq!(spec.report_kind, "claude_chat_sync_success");
|
||||
assert!(!spec.require_streaming);
|
||||
}
|
||||
@@ -102,7 +102,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_gemini_stream_same_format_spec() {
|
||||
let spec = resolve_stream_spec("gemini_cli_stream").expect("spec");
|
||||
assert_eq!(spec.api_format, "gemini:cli");
|
||||
assert_eq!(spec.api_format, "gemini:generate_content");
|
||||
assert_eq!(spec.report_kind, "gemini_cli_stream_success");
|
||||
assert!(spec.require_streaming);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn resolve_execution_runtime_stream_plan_kind(
|
||||
}
|
||||
|
||||
if route_family == Some("claude")
|
||||
&& route_kind == Some("chat")
|
||||
&& is_claude_messages_route_kind(route_kind)
|
||||
&& *method == Method::POST
|
||||
&& path == "/v1/messages"
|
||||
{
|
||||
@@ -59,7 +59,7 @@ pub fn resolve_execution_runtime_stream_plan_kind(
|
||||
}
|
||||
|
||||
if route_family == Some("gemini")
|
||||
&& route_kind == Some("chat")
|
||||
&& is_gemini_generate_content_route_kind(route_kind)
|
||||
&& *method == Method::POST
|
||||
&& path.ends_with(":streamGenerateContent")
|
||||
{
|
||||
@@ -206,7 +206,7 @@ pub fn resolve_execution_runtime_sync_plan_kind(
|
||||
}
|
||||
|
||||
if route_family == Some("claude")
|
||||
&& route_kind == Some("chat")
|
||||
&& is_claude_messages_route_kind(route_kind)
|
||||
&& *method == Method::POST
|
||||
&& path == "/v1/messages"
|
||||
{
|
||||
@@ -222,7 +222,7 @@ pub fn resolve_execution_runtime_sync_plan_kind(
|
||||
}
|
||||
|
||||
if route_family == Some("gemini")
|
||||
&& route_kind == Some("chat")
|
||||
&& is_gemini_generate_content_route_kind(route_kind)
|
||||
&& *method == Method::POST
|
||||
&& path.ends_with(":generateContent")
|
||||
{
|
||||
@@ -269,6 +269,14 @@ fn is_openai_responses_compact_route_kind(route_kind: Option<&str>) -> bool {
|
||||
matches!(route_kind, Some("responses:compact") | Some("compact"))
|
||||
}
|
||||
|
||||
fn is_claude_messages_route_kind(route_kind: Option<&str>) -> bool {
|
||||
matches!(route_kind, Some("messages") | Some("chat"))
|
||||
}
|
||||
|
||||
fn is_gemini_generate_content_route_kind(route_kind: Option<&str>) -> bool {
|
||||
matches!(route_kind, Some("generate_content") | Some("chat"))
|
||||
}
|
||||
|
||||
pub fn is_matching_stream_request(
|
||||
plan_kind: &str,
|
||||
path: &str,
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::planner::standard::family::{
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
match plan_kind {
|
||||
CLAUDE_CHAT_SYNC_PLAN_KIND => Some(LocalStandardSpec {
|
||||
api_format: "claude:chat",
|
||||
api_format: "claude:messages",
|
||||
decision_kind: CLAUDE_CHAT_SYNC_PLAN_KIND,
|
||||
report_kind: "claude_chat_sync_finalize",
|
||||
family: LocalStandardSourceFamily::Standard,
|
||||
@@ -20,7 +20,7 @@ pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
match plan_kind {
|
||||
CLAUDE_CHAT_STREAM_PLAN_KIND => Some(LocalStandardSpec {
|
||||
api_format: "claude:chat",
|
||||
api_format: "claude:messages",
|
||||
decision_kind: CLAUDE_CHAT_STREAM_PLAN_KIND,
|
||||
report_kind: "claude_chat_stream_success",
|
||||
family: LocalStandardSourceFamily::Standard,
|
||||
@@ -38,7 +38,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_claude_chat_sync_spec() {
|
||||
let spec = resolve_sync_spec("claude_chat_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "claude:chat");
|
||||
assert_eq!(spec.api_format, "claude:messages");
|
||||
assert_eq!(spec.report_kind, "claude_chat_sync_finalize");
|
||||
assert!(!spec.require_streaming);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_claude_chat_stream_spec() {
|
||||
let spec = resolve_stream_spec("claude_chat_stream").expect("spec");
|
||||
assert_eq!(spec.api_format, "claude:chat");
|
||||
assert_eq!(spec.api_format, "claude:messages");
|
||||
assert_eq!(spec.report_kind, "claude_chat_stream_success");
|
||||
assert!(spec.require_streaming);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::planner::standard::family::{
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
match plan_kind {
|
||||
CLAUDE_CLI_SYNC_PLAN_KIND => Some(LocalStandardSpec {
|
||||
api_format: "claude:cli",
|
||||
api_format: "claude:messages",
|
||||
decision_kind: CLAUDE_CLI_SYNC_PLAN_KIND,
|
||||
report_kind: "claude_cli_sync_finalize",
|
||||
family: LocalStandardSourceFamily::Standard,
|
||||
@@ -20,7 +20,7 @@ pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
match plan_kind {
|
||||
CLAUDE_CLI_STREAM_PLAN_KIND => Some(LocalStandardSpec {
|
||||
api_format: "claude:cli",
|
||||
api_format: "claude:messages",
|
||||
decision_kind: CLAUDE_CLI_STREAM_PLAN_KIND,
|
||||
report_kind: "claude_cli_stream_success",
|
||||
family: LocalStandardSourceFamily::Standard,
|
||||
@@ -38,7 +38,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_claude_cli_sync_spec() {
|
||||
let spec = resolve_sync_spec("claude_cli_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "claude:cli");
|
||||
assert_eq!(spec.api_format, "claude:messages");
|
||||
assert_eq!(spec.report_kind, "claude_cli_sync_finalize");
|
||||
assert!(!spec.require_streaming);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_claude_cli_stream_spec() {
|
||||
let spec = resolve_stream_spec("claude_cli_stream").expect("spec");
|
||||
assert_eq!(spec.api_format, "claude:cli");
|
||||
assert_eq!(spec.api_format, "claude:messages");
|
||||
assert_eq!(spec.report_kind, "claude_cli_stream_success");
|
||||
assert!(spec.require_streaming);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::planner::standard::family::{
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
match plan_kind {
|
||||
GEMINI_CHAT_SYNC_PLAN_KIND => Some(LocalStandardSpec {
|
||||
api_format: "gemini:chat",
|
||||
api_format: "gemini:generate_content",
|
||||
decision_kind: GEMINI_CHAT_SYNC_PLAN_KIND,
|
||||
report_kind: "gemini_chat_sync_finalize",
|
||||
family: LocalStandardSourceFamily::Gemini,
|
||||
@@ -20,7 +20,7 @@ pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
match plan_kind {
|
||||
GEMINI_CHAT_STREAM_PLAN_KIND => Some(LocalStandardSpec {
|
||||
api_format: "gemini:chat",
|
||||
api_format: "gemini:generate_content",
|
||||
decision_kind: GEMINI_CHAT_STREAM_PLAN_KIND,
|
||||
report_kind: "gemini_chat_stream_success",
|
||||
family: LocalStandardSourceFamily::Gemini,
|
||||
@@ -38,7 +38,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_gemini_chat_sync_spec() {
|
||||
let spec = resolve_sync_spec("gemini_chat_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "gemini:chat");
|
||||
assert_eq!(spec.api_format, "gemini:generate_content");
|
||||
assert_eq!(spec.report_kind, "gemini_chat_sync_finalize");
|
||||
assert!(!spec.require_streaming);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_gemini_chat_stream_spec() {
|
||||
let spec = resolve_stream_spec("gemini_chat_stream").expect("spec");
|
||||
assert_eq!(spec.api_format, "gemini:chat");
|
||||
assert_eq!(spec.api_format, "gemini:generate_content");
|
||||
assert_eq!(spec.report_kind, "gemini_chat_stream_success");
|
||||
assert!(spec.require_streaming);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::planner::standard::family::{
|
||||
pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
match plan_kind {
|
||||
GEMINI_CLI_SYNC_PLAN_KIND => Some(LocalStandardSpec {
|
||||
api_format: "gemini:cli",
|
||||
api_format: "gemini:generate_content",
|
||||
decision_kind: GEMINI_CLI_SYNC_PLAN_KIND,
|
||||
report_kind: "gemini_cli_sync_finalize",
|
||||
family: LocalStandardSourceFamily::Gemini,
|
||||
@@ -20,7 +20,7 @@ pub fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
pub fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
|
||||
match plan_kind {
|
||||
GEMINI_CLI_STREAM_PLAN_KIND => Some(LocalStandardSpec {
|
||||
api_format: "gemini:cli",
|
||||
api_format: "gemini:generate_content",
|
||||
decision_kind: GEMINI_CLI_STREAM_PLAN_KIND,
|
||||
report_kind: "gemini_cli_stream_success",
|
||||
family: LocalStandardSourceFamily::Gemini,
|
||||
@@ -38,7 +38,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_gemini_cli_sync_spec() {
|
||||
let spec = resolve_sync_spec("gemini_cli_sync").expect("spec");
|
||||
assert_eq!(spec.api_format, "gemini:cli");
|
||||
assert_eq!(spec.api_format, "gemini:generate_content");
|
||||
assert_eq!(spec.report_kind, "gemini_cli_sync_finalize");
|
||||
assert!(!spec.require_streaming);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ mod tests {
|
||||
#[test]
|
||||
fn resolves_gemini_cli_stream_spec() {
|
||||
let spec = resolve_stream_spec("gemini_cli_stream").expect("spec");
|
||||
assert_eq!(spec.api_format, "gemini:cli");
|
||||
assert_eq!(spec.api_format, "gemini:generate_content");
|
||||
assert_eq!(spec.report_kind, "gemini_cli_stream_success");
|
||||
assert!(spec.require_streaming);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ pub fn build_standard_request_body_from_canonical(
|
||||
provider_api_format: &str,
|
||||
upstream_is_stream: bool,
|
||||
) -> Option<Value> {
|
||||
match aether_ai_formats::normalize_legacy_openai_format_alias(provider_api_format).as_str() {
|
||||
match aether_ai_formats::normalize_api_format_alias(provider_api_format).as_str() {
|
||||
"openai:chat" => build_local_openai_chat_request_body(
|
||||
canonical_request,
|
||||
mapped_model,
|
||||
@@ -85,12 +85,12 @@ pub fn build_standard_request_body_from_canonical(
|
||||
false,
|
||||
true,
|
||||
),
|
||||
"claude:chat" | "claude:cli" => convert_openai_chat_request_to_claude_request(
|
||||
"claude:messages" => convert_openai_chat_request_to_claude_request(
|
||||
canonical_request,
|
||||
mapped_model,
|
||||
upstream_is_stream,
|
||||
),
|
||||
"gemini:chat" | "gemini:cli" => convert_openai_chat_request_to_gemini_request(
|
||||
"gemini:generate_content" => convert_openai_chat_request_to_gemini_request(
|
||||
canonical_request,
|
||||
mapped_model,
|
||||
upstream_is_stream,
|
||||
@@ -117,15 +117,15 @@ fn normalize_standard_request_to_openai_chat_request_cow<'a>(
|
||||
client_api_format: &str,
|
||||
request_path: &str,
|
||||
) -> Option<Cow<'a, Value>> {
|
||||
match aether_ai_formats::normalize_legacy_openai_format_alias(client_api_format).as_str() {
|
||||
match aether_ai_formats::normalize_api_format_alias(client_api_format).as_str() {
|
||||
"openai:chat" => Some(Cow::Borrowed(body_json)),
|
||||
"openai:responses" | "openai:responses:compact" => {
|
||||
normalize_openai_responses_request_to_openai_chat_request(body_json).map(Cow::Owned)
|
||||
}
|
||||
"claude:chat" | "claude:cli" => {
|
||||
"claude:messages" => {
|
||||
normalize_claude_request_to_openai_chat_request(body_json).map(Cow::Owned)
|
||||
}
|
||||
"gemini:chat" | "gemini:cli" => {
|
||||
"gemini:generate_content" => {
|
||||
normalize_gemini_request_to_openai_chat_request(body_json, request_path).map(Cow::Owned)
|
||||
}
|
||||
_ => None,
|
||||
@@ -162,10 +162,8 @@ mod tests {
|
||||
const STANDARD_SURFACES: &[&str] = &[
|
||||
"openai:chat",
|
||||
"openai:responses",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"gemini:chat",
|
||||
"gemini:cli",
|
||||
"claude:messages",
|
||||
"gemini:generate_content",
|
||||
];
|
||||
|
||||
fn sample_request_for(api_format: &str) -> (Value, &'static str) {
|
||||
@@ -190,7 +188,7 @@ mod tests {
|
||||
}),
|
||||
"/v1/responses",
|
||||
),
|
||||
"claude:chat" | "claude:cli" => (
|
||||
"claude:messages" => (
|
||||
json!({
|
||||
"model": "source-model",
|
||||
"system": "Be concise.",
|
||||
@@ -202,7 +200,7 @@ mod tests {
|
||||
}),
|
||||
"/v1/messages",
|
||||
),
|
||||
"gemini:chat" | "gemini:cli" => (
|
||||
"gemini:generate_content" => (
|
||||
json!({
|
||||
"systemInstruction": {
|
||||
"parts": [{"text": "Be concise."}]
|
||||
@@ -223,7 +221,7 @@ mod tests {
|
||||
|
||||
fn assert_stream_flag(provider_api_format: &str, upstream_is_stream: bool, converted: &Value) {
|
||||
match provider_api_format {
|
||||
"openai:chat" | "openai:responses" | "claude:chat" | "claude:cli" => {
|
||||
"openai:chat" | "openai:responses" | "claude:messages" => {
|
||||
assert_eq!(
|
||||
converted
|
||||
.get("stream")
|
||||
@@ -233,7 +231,7 @@ mod tests {
|
||||
"{provider_api_format} stream flag should follow upstream_is_stream"
|
||||
);
|
||||
}
|
||||
"gemini:chat" | "gemini:cli" => {
|
||||
"gemini:generate_content" => {
|
||||
assert!(
|
||||
converted.get("stream").is_none(),
|
||||
"gemini streaming is represented by endpoint URL, not request body"
|
||||
@@ -299,7 +297,7 @@ mod tests {
|
||||
) -> Value {
|
||||
let chat_canonical = normalize_standard_request_to_openai_chat_request(
|
||||
request,
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"/v1/messages",
|
||||
)
|
||||
.expect("legacy claude normalization should succeed");
|
||||
@@ -319,7 +317,7 @@ mod tests {
|
||||
) -> Value {
|
||||
let chat_canonical = normalize_standard_request_to_openai_chat_request(
|
||||
request,
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"/v1beta/models/source-model:generateContent",
|
||||
)
|
||||
.expect("legacy gemini normalization should succeed");
|
||||
@@ -607,13 +605,12 @@ mod tests {
|
||||
"openai:chat",
|
||||
"openai:responses",
|
||||
"openai:responses:compact",
|
||||
"gemini:chat",
|
||||
"gemini:cli",
|
||||
"gemini:generate_content",
|
||||
] {
|
||||
for upstream_is_stream in [false, true] {
|
||||
let converted = build_standard_request_body(
|
||||
&request,
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"mapped-model",
|
||||
"custom",
|
||||
provider_api_format,
|
||||
@@ -627,7 +624,7 @@ mod tests {
|
||||
legacy_claude_request_body(&request, provider_api_format, upstream_is_stream);
|
||||
assert_eq!(
|
||||
converted, legacy,
|
||||
"typed canonical claude:chat -> {provider_api_format} changed payload with upstream_is_stream={upstream_is_stream}"
|
||||
"typed canonical claude:messages -> {provider_api_format} changed payload with upstream_is_stream={upstream_is_stream}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -689,13 +686,12 @@ mod tests {
|
||||
"openai:chat",
|
||||
"openai:responses",
|
||||
"openai:responses:compact",
|
||||
"claude:chat",
|
||||
"claude:cli",
|
||||
"claude:messages",
|
||||
] {
|
||||
for upstream_is_stream in [false, true] {
|
||||
let converted = build_standard_request_body(
|
||||
&request,
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"mapped-model",
|
||||
"custom",
|
||||
provider_api_format,
|
||||
@@ -709,7 +705,7 @@ mod tests {
|
||||
legacy_gemini_request_body(&request, provider_api_format, upstream_is_stream);
|
||||
assert_eq!(
|
||||
converted, legacy,
|
||||
"typed canonical gemini:chat -> {provider_api_format} changed payload with upstream_is_stream={upstream_is_stream}"
|
||||
"typed canonical gemini:generate_content -> {provider_api_format} changed payload with upstream_is_stream={upstream_is_stream}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -770,7 +766,7 @@ mod tests {
|
||||
|
||||
let converted = build_standard_request_body(
|
||||
&request,
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"gpt-5",
|
||||
"openai",
|
||||
"openai:chat",
|
||||
@@ -801,7 +797,7 @@ mod tests {
|
||||
|
||||
let converted = build_standard_request_body(
|
||||
&request,
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"gpt-5",
|
||||
"openai",
|
||||
"openai:chat",
|
||||
@@ -835,10 +831,10 @@ mod tests {
|
||||
|
||||
let converted = build_standard_request_body(
|
||||
&request,
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"claude-sonnet-4-5",
|
||||
"anthropic",
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"/v1beta/models/gemini-2.5-pro:generateContent",
|
||||
false,
|
||||
None,
|
||||
@@ -871,10 +867,10 @@ mod tests {
|
||||
|
||||
let converted = build_standard_request_body(
|
||||
&request,
|
||||
"claude:cli",
|
||||
"claude:messages",
|
||||
"gemini-2.5-pro",
|
||||
"google",
|
||||
"gemini:cli",
|
||||
"gemini:generate_content",
|
||||
"/v1/messages",
|
||||
false,
|
||||
None,
|
||||
@@ -1000,7 +996,7 @@ mod tests {
|
||||
"openai:chat",
|
||||
"gemini-2.5-pro",
|
||||
"google",
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"/v1/chat/completions",
|
||||
false,
|
||||
None,
|
||||
@@ -1052,7 +1048,7 @@ mod tests {
|
||||
"openai:chat",
|
||||
"claude-sonnet-4-5",
|
||||
"anthropic",
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"/v1/chat/completions",
|
||||
false,
|
||||
None,
|
||||
@@ -1073,7 +1069,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openai_responses_nested_tools_survive_claude_cli_and_kiro_envelope_conversion() {
|
||||
fn openai_responses_nested_tools_survive_claude_messages_and_kiro_envelope_conversion() {
|
||||
let request = json!({
|
||||
"model": "gpt-5",
|
||||
"input": "Use the weather tool for Shanghai.",
|
||||
@@ -1102,13 +1098,13 @@ mod tests {
|
||||
"openai:responses",
|
||||
"claude-sonnet-4.6",
|
||||
"kiro",
|
||||
"claude:cli",
|
||||
"claude:messages",
|
||||
"/v1/responses",
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect("openai responses should convert to claude cli");
|
||||
.expect("openai responses should convert to claude messages");
|
||||
assert_eq!(claude["tools"][0]["name"], "get_weather");
|
||||
assert_eq!(claude["tool_choice"]["name"], "get_weather");
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ mod tests {
|
||||
80
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_input_tokens_for_billing(Some("gemini:chat"), 100, 20),
|
||||
normalize_input_tokens_for_billing(Some("gemini:generate_content"), 100, 20),
|
||||
80
|
||||
);
|
||||
}
|
||||
@@ -95,7 +95,7 @@ mod tests {
|
||||
#[test]
|
||||
fn keeps_input_tokens_for_claude() {
|
||||
assert_eq!(
|
||||
normalize_input_tokens_for_billing(Some("claude:chat"), 100, 20),
|
||||
normalize_input_tokens_for_billing(Some("claude:messages"), 100, 20),
|
||||
100
|
||||
);
|
||||
}
|
||||
@@ -107,7 +107,12 @@ mod tests {
|
||||
120
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_total_input_context_for_cache_hit_rate(Some("gemini:chat"), 120, 10, 15),
|
||||
normalize_total_input_context_for_cache_hit_rate(
|
||||
Some("gemini:generate_content"),
|
||||
120,
|
||||
10,
|
||||
15
|
||||
),
|
||||
120
|
||||
);
|
||||
}
|
||||
@@ -115,7 +120,7 @@ mod tests {
|
||||
#[test]
|
||||
fn includes_cache_creation_for_claude_cache_hit_context() {
|
||||
assert_eq!(
|
||||
normalize_total_input_context_for_cache_hit_rate(Some("claude:chat"), 60, 15, 5),
|
||||
normalize_total_input_context_for_cache_hit_rate(Some("claude:messages"), 60, 15, 5),
|
||||
80
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,12 +57,8 @@ impl StoredMinimalCandidateSelectionRow {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_api_format(value: &str) -> String {
|
||||
aether_ai_formats::normalize_legacy_openai_format_alias(value)
|
||||
}
|
||||
|
||||
fn api_format_matches(left: &str, right: &str) -> bool {
|
||||
normalize_api_format(left) == normalize_api_format(right)
|
||||
aether_ai_formats::api_format_alias_matches(left, right)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
CREATE OR REPLACE FUNCTION public.aether_canonical_api_format_alias(value text)
|
||||
RETURNS text
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
AS $$
|
||||
SELECT CASE LOWER(BTRIM(COALESCE(value, '')))
|
||||
WHEN 'openai:cli' THEN 'openai:responses'
|
||||
WHEN 'openai:compact' THEN 'openai:responses:compact'
|
||||
WHEN 'claude:chat' THEN 'claude:messages'
|
||||
WHEN 'claude:cli' THEN 'claude:messages'
|
||||
WHEN 'gemini:chat' THEN 'gemini:generate_content'
|
||||
WHEN 'gemini:cli' THEN 'gemini:generate_content'
|
||||
ELSE LOWER(BTRIM(COALESCE(value, '')))
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
conflict_summary text;
|
||||
BEGIN
|
||||
SELECT string_agg(
|
||||
DISTINCT provider_id::text || ':' || canonical_api_format,
|
||||
', ' ORDER BY provider_id::text || ':' || canonical_api_format
|
||||
)
|
||||
INTO conflict_summary
|
||||
FROM (
|
||||
SELECT
|
||||
left_endpoint.provider_id,
|
||||
public.aether_canonical_api_format_alias(left_endpoint.api_format) AS canonical_api_format
|
||||
FROM public.provider_endpoints AS left_endpoint
|
||||
INNER JOIN public.provider_endpoints AS right_endpoint
|
||||
ON right_endpoint.provider_id = left_endpoint.provider_id
|
||||
AND right_endpoint.id > left_endpoint.id
|
||||
AND public.aether_canonical_api_format_alias(right_endpoint.api_format)
|
||||
= public.aether_canonical_api_format_alias(left_endpoint.api_format)
|
||||
WHERE left_endpoint.api_format IN ('openai:responses', 'openai:cli', 'openai:responses:compact', 'openai:compact', 'claude:messages', 'claude:chat', 'claude:cli', 'gemini:generate_content', 'gemini:chat', 'gemini:cli')
|
||||
AND right_endpoint.api_format IN ('openai:responses', 'openai:cli', 'openai:responses:compact', 'openai:compact', 'claude:messages', 'claude:chat', 'claude:cli', 'gemini:generate_content', 'gemini:chat', 'gemini:cli')
|
||||
AND (
|
||||
left_endpoint.base_url IS DISTINCT FROM right_endpoint.base_url
|
||||
OR left_endpoint.custom_path IS DISTINCT FROM right_endpoint.custom_path
|
||||
OR left_endpoint.max_retries IS DISTINCT FROM right_endpoint.max_retries
|
||||
OR left_endpoint.header_rules::jsonb IS DISTINCT FROM right_endpoint.header_rules::jsonb
|
||||
OR left_endpoint.body_rules::jsonb IS DISTINCT FROM right_endpoint.body_rules::jsonb
|
||||
OR left_endpoint.config::jsonb IS DISTINCT FROM right_endpoint.config::jsonb
|
||||
OR left_endpoint.proxy IS DISTINCT FROM right_endpoint.proxy
|
||||
OR left_endpoint.format_acceptance_config::jsonb IS DISTINCT FROM right_endpoint.format_acceptance_config::jsonb
|
||||
)
|
||||
) AS conflicts;
|
||||
|
||||
IF conflict_summary IS NOT NULL THEN
|
||||
RAISE EXCEPTION
|
||||
'Cannot normalize OpenAI/Claude/Gemini provider_endpoints because transport fields differ for: %',
|
||||
conflict_summary;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
WITH grouped AS (
|
||||
SELECT
|
||||
id,
|
||||
provider_id,
|
||||
api_format,
|
||||
public.aether_canonical_api_format_alias(api_format) AS canonical_api_format,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY provider_id, public.aether_canonical_api_format_alias(api_format)
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN api_format = public.aether_canonical_api_format_alias(api_format) THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
created_at ASC,
|
||||
id ASC
|
||||
) AS rank
|
||||
FROM public.provider_endpoints
|
||||
WHERE api_format IN ('openai:responses', 'openai:cli', 'openai:responses:compact', 'openai:compact', 'claude:messages', 'claude:chat', 'claude:cli', 'gemini:generate_content', 'gemini:chat', 'gemini:cli')
|
||||
),
|
||||
survivors AS (
|
||||
SELECT *
|
||||
FROM grouped
|
||||
WHERE rank = 1
|
||||
),
|
||||
retired AS (
|
||||
UPDATE public.provider_endpoints AS endpoint
|
||||
SET
|
||||
is_active = FALSE,
|
||||
updated_at = NOW()
|
||||
FROM grouped
|
||||
WHERE endpoint.id = grouped.id
|
||||
AND grouped.rank > 1
|
||||
RETURNING endpoint.id
|
||||
)
|
||||
UPDATE public.provider_endpoints AS endpoint
|
||||
SET
|
||||
api_format = survivors.canonical_api_format,
|
||||
api_family = SPLIT_PART(survivors.canonical_api_format, ':', 1),
|
||||
endpoint_kind = SUBSTRING(survivors.canonical_api_format FROM POSITION(':' IN survivors.canonical_api_format) + 1),
|
||||
updated_at = NOW()
|
||||
FROM survivors
|
||||
WHERE endpoint.id = survivors.id
|
||||
AND endpoint.api_format IS DISTINCT FROM survivors.canonical_api_format;
|
||||
|
||||
WITH expanded AS (
|
||||
SELECT
|
||||
pak.id,
|
||||
formats.ordinality,
|
||||
public.aether_canonical_api_format_alias(formats.value) AS api_format
|
||||
FROM public.provider_api_keys AS pak
|
||||
CROSS JOIN LATERAL json_array_elements_text(
|
||||
CASE
|
||||
WHEN json_typeof(pak.api_formats) = 'array' THEN pak.api_formats
|
||||
ELSE '[]'::json
|
||||
END
|
||||
) WITH ORDINALITY AS formats(value, ordinality)
|
||||
WHERE pak.api_formats IS NOT NULL
|
||||
),
|
||||
deduped AS (
|
||||
SELECT id, api_format, MIN(ordinality) AS first_ordinality
|
||||
FROM expanded
|
||||
WHERE api_format <> ''
|
||||
GROUP BY id, api_format
|
||||
),
|
||||
rebuilt AS (
|
||||
SELECT id, json_agg(api_format ORDER BY first_ordinality) AS api_formats
|
||||
FROM deduped
|
||||
GROUP BY id
|
||||
)
|
||||
UPDATE public.provider_api_keys AS pak
|
||||
SET
|
||||
api_formats = rebuilt.api_formats,
|
||||
updated_at = NOW()
|
||||
FROM rebuilt
|
||||
WHERE pak.id = rebuilt.id
|
||||
AND pak.api_formats::jsonb IS DISTINCT FROM rebuilt.api_formats::jsonb;
|
||||
|
||||
WITH expanded AS (
|
||||
SELECT
|
||||
key.id,
|
||||
formats.ordinality,
|
||||
public.aether_canonical_api_format_alias(formats.value) AS api_format
|
||||
FROM public.api_keys AS key
|
||||
CROSS JOIN LATERAL json_array_elements_text(
|
||||
CASE
|
||||
WHEN json_typeof(key.allowed_api_formats) = 'array' THEN key.allowed_api_formats
|
||||
ELSE '[]'::json
|
||||
END
|
||||
) WITH ORDINALITY AS formats(value, ordinality)
|
||||
WHERE key.allowed_api_formats IS NOT NULL
|
||||
),
|
||||
deduped AS (
|
||||
SELECT id, api_format, MIN(ordinality) AS first_ordinality
|
||||
FROM expanded
|
||||
WHERE api_format <> ''
|
||||
GROUP BY id, api_format
|
||||
),
|
||||
rebuilt AS (
|
||||
SELECT id, json_agg(api_format ORDER BY first_ordinality) AS allowed_api_formats
|
||||
FROM deduped
|
||||
GROUP BY id
|
||||
)
|
||||
UPDATE public.api_keys AS key
|
||||
SET
|
||||
allowed_api_formats = rebuilt.allowed_api_formats,
|
||||
updated_at = NOW()
|
||||
FROM rebuilt
|
||||
WHERE key.id = rebuilt.id
|
||||
AND key.allowed_api_formats::jsonb IS DISTINCT FROM rebuilt.allowed_api_formats::jsonb;
|
||||
|
||||
WITH expanded AS (
|
||||
SELECT
|
||||
users.id,
|
||||
formats.ordinality,
|
||||
public.aether_canonical_api_format_alias(formats.value) AS api_format
|
||||
FROM public.users AS users
|
||||
CROSS JOIN LATERAL json_array_elements_text(
|
||||
CASE
|
||||
WHEN json_typeof(users.allowed_api_formats) = 'array' THEN users.allowed_api_formats
|
||||
ELSE '[]'::json
|
||||
END
|
||||
) WITH ORDINALITY AS formats(value, ordinality)
|
||||
WHERE users.allowed_api_formats IS NOT NULL
|
||||
),
|
||||
deduped AS (
|
||||
SELECT id, api_format, MIN(ordinality) AS first_ordinality
|
||||
FROM expanded
|
||||
WHERE api_format <> ''
|
||||
GROUP BY id, api_format
|
||||
),
|
||||
rebuilt AS (
|
||||
SELECT id, json_agg(api_format ORDER BY first_ordinality) AS allowed_api_formats
|
||||
FROM deduped
|
||||
GROUP BY id
|
||||
)
|
||||
UPDATE public.users AS users
|
||||
SET
|
||||
allowed_api_formats = rebuilt.allowed_api_formats,
|
||||
updated_at = NOW()
|
||||
FROM rebuilt
|
||||
WHERE users.id = rebuilt.id
|
||||
AND users.allowed_api_formats::jsonb IS DISTINCT FROM rebuilt.allowed_api_formats::jsonb;
|
||||
|
||||
WITH mapping_items AS (
|
||||
SELECT
|
||||
models.id,
|
||||
item.ordinality AS item_ordinality,
|
||||
item.value AS item
|
||||
FROM public.models AS models
|
||||
CROSS JOIN LATERAL jsonb_array_elements(
|
||||
CASE
|
||||
WHEN jsonb_typeof(models.provider_model_mappings) = 'array' THEN models.provider_model_mappings
|
||||
ELSE '[]'::jsonb
|
||||
END
|
||||
) WITH ORDINALITY AS item(value, ordinality)
|
||||
WHERE models.provider_model_mappings IS NOT NULL
|
||||
),
|
||||
rebuilt_items AS (
|
||||
SELECT
|
||||
id,
|
||||
item_ordinality,
|
||||
CASE
|
||||
WHEN jsonb_typeof(item) = 'object'
|
||||
AND jsonb_typeof(item->'api_formats') = 'array'
|
||||
THEN jsonb_set(
|
||||
item,
|
||||
'{api_formats}',
|
||||
COALESCE(
|
||||
(
|
||||
SELECT jsonb_agg(api_format ORDER BY first_ordinality)
|
||||
FROM (
|
||||
SELECT
|
||||
public.aether_canonical_api_format_alias(format.value) AS api_format,
|
||||
MIN(format.ordinality) AS first_ordinality
|
||||
FROM jsonb_array_elements_text(item->'api_formats') WITH ORDINALITY AS format(value, ordinality)
|
||||
GROUP BY public.aether_canonical_api_format_alias(format.value)
|
||||
) AS deduped_formats
|
||||
WHERE api_format <> ''
|
||||
),
|
||||
'[]'::jsonb
|
||||
),
|
||||
true
|
||||
)
|
||||
ELSE item
|
||||
END AS item
|
||||
FROM mapping_items
|
||||
),
|
||||
rebuilt AS (
|
||||
SELECT id, jsonb_agg(item ORDER BY item_ordinality) AS provider_model_mappings
|
||||
FROM rebuilt_items
|
||||
GROUP BY id
|
||||
)
|
||||
UPDATE public.models AS models
|
||||
SET
|
||||
provider_model_mappings = rebuilt.provider_model_mappings,
|
||||
updated_at = NOW()
|
||||
FROM rebuilt
|
||||
WHERE models.id = rebuilt.id
|
||||
AND models.provider_model_mappings IS DISTINCT FROM rebuilt.provider_model_mappings;
|
||||
|
||||
WITH rebuilt AS (
|
||||
SELECT
|
||||
pak.id,
|
||||
jsonb_object_agg(
|
||||
public.aether_canonical_api_format_alias(entry.key),
|
||||
entry.value
|
||||
ORDER BY entry.ordinality
|
||||
) AS rate_multipliers
|
||||
FROM public.provider_api_keys AS pak
|
||||
CROSS JOIN LATERAL json_each(
|
||||
CASE
|
||||
WHEN json_typeof(pak.rate_multipliers) = 'object' THEN pak.rate_multipliers
|
||||
ELSE '{}'::json
|
||||
END
|
||||
) WITH ORDINALITY AS entry(key, value, ordinality)
|
||||
WHERE pak.rate_multipliers IS NOT NULL
|
||||
GROUP BY pak.id
|
||||
)
|
||||
UPDATE public.provider_api_keys AS pak
|
||||
SET
|
||||
rate_multipliers = rebuilt.rate_multipliers::json,
|
||||
updated_at = NOW()
|
||||
FROM rebuilt
|
||||
WHERE pak.id = rebuilt.id
|
||||
AND pak.rate_multipliers::jsonb IS DISTINCT FROM rebuilt.rate_multipliers;
|
||||
|
||||
WITH rebuilt AS (
|
||||
SELECT
|
||||
pak.id,
|
||||
jsonb_object_agg(
|
||||
public.aether_canonical_api_format_alias(entry.key),
|
||||
entry.value
|
||||
ORDER BY entry.ordinality
|
||||
) AS global_priority_by_format
|
||||
FROM public.provider_api_keys AS pak
|
||||
CROSS JOIN LATERAL json_each(
|
||||
CASE
|
||||
WHEN json_typeof(pak.global_priority_by_format) = 'object' THEN pak.global_priority_by_format
|
||||
ELSE '{}'::json
|
||||
END
|
||||
) WITH ORDINALITY AS entry(key, value, ordinality)
|
||||
WHERE pak.global_priority_by_format IS NOT NULL
|
||||
GROUP BY pak.id
|
||||
)
|
||||
UPDATE public.provider_api_keys AS pak
|
||||
SET
|
||||
global_priority_by_format = rebuilt.global_priority_by_format::json,
|
||||
updated_at = NOW()
|
||||
FROM rebuilt
|
||||
WHERE pak.id = rebuilt.id
|
||||
AND pak.global_priority_by_format::jsonb IS DISTINCT FROM rebuilt.global_priority_by_format;
|
||||
|
||||
WITH rebuilt AS (
|
||||
SELECT
|
||||
pak.id,
|
||||
jsonb_object_agg(
|
||||
public.aether_canonical_api_format_alias(entry.key),
|
||||
entry.value
|
||||
ORDER BY entry.ordinality
|
||||
) AS health_by_format
|
||||
FROM public.provider_api_keys AS pak
|
||||
CROSS JOIN LATERAL jsonb_each(
|
||||
CASE
|
||||
WHEN jsonb_typeof(pak.health_by_format) = 'object' THEN pak.health_by_format
|
||||
ELSE '{}'::jsonb
|
||||
END
|
||||
) WITH ORDINALITY AS entry(key, value, ordinality)
|
||||
WHERE pak.health_by_format IS NOT NULL
|
||||
GROUP BY pak.id
|
||||
)
|
||||
UPDATE public.provider_api_keys AS pak
|
||||
SET
|
||||
health_by_format = rebuilt.health_by_format,
|
||||
updated_at = NOW()
|
||||
FROM rebuilt
|
||||
WHERE pak.id = rebuilt.id
|
||||
AND pak.health_by_format IS DISTINCT FROM rebuilt.health_by_format;
|
||||
|
||||
WITH rebuilt AS (
|
||||
SELECT
|
||||
pak.id,
|
||||
jsonb_object_agg(
|
||||
public.aether_canonical_api_format_alias(entry.key),
|
||||
entry.value
|
||||
ORDER BY entry.ordinality
|
||||
) AS circuit_breaker_by_format
|
||||
FROM public.provider_api_keys AS pak
|
||||
CROSS JOIN LATERAL jsonb_each(
|
||||
CASE
|
||||
WHEN jsonb_typeof(pak.circuit_breaker_by_format) = 'object' THEN pak.circuit_breaker_by_format
|
||||
ELSE '{}'::jsonb
|
||||
END
|
||||
) WITH ORDINALITY AS entry(key, value, ordinality)
|
||||
WHERE pak.circuit_breaker_by_format IS NOT NULL
|
||||
GROUP BY pak.id
|
||||
)
|
||||
UPDATE public.provider_api_keys AS pak
|
||||
SET
|
||||
circuit_breaker_by_format = rebuilt.circuit_breaker_by_format,
|
||||
updated_at = NOW()
|
||||
FROM rebuilt
|
||||
WHERE pak.id = rebuilt.id
|
||||
AND pak.circuit_breaker_by_format IS DISTINCT FROM rebuilt.circuit_breaker_by_format;
|
||||
|
||||
DROP FUNCTION public.aether_canonical_api_format_alias(text);
|
||||
@@ -8,7 +8,7 @@ use tracing::{error, info, warn};
|
||||
|
||||
static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
|
||||
static BASELINE_V2_SQL: &str = include_str!("../bootstrap/20260413020000_baseline_v2.sql");
|
||||
const BASELINE_V2_CUTOFF_VERSION: i64 = 20260424000000;
|
||||
const BASELINE_V2_CUTOFF_VERSION: i64 = 20260428000000;
|
||||
const MIGRATIONS_TABLE_EXISTS_SQL: &str =
|
||||
"SELECT to_regclass('public._sqlx_migrations') IS NOT NULL";
|
||||
const PUBLIC_BASE_TABLE_COUNT_SQL: &str = r#"
|
||||
@@ -664,6 +664,7 @@ SELECT EXISTS (
|
||||
20260422120000,
|
||||
20260423000000,
|
||||
20260424000000,
|
||||
20260428000000,
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -816,6 +817,7 @@ SELECT EXISTS (
|
||||
20260422120000,
|
||||
20260423000000,
|
||||
20260424000000,
|
||||
20260428000000,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -913,7 +913,7 @@ mod tests {
|
||||
None,
|
||||
None,
|
||||
Some(serde_json::json!(["anthropic"])),
|
||||
Some(serde_json::json!(["claude:chat"])),
|
||||
Some(serde_json::json!(["claude:messages"])),
|
||||
Some(serde_json::json!(["claude-sonnet-4-5"])),
|
||||
)
|
||||
.expect("snapshot should build");
|
||||
@@ -926,7 +926,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.effective_allowed_api_formats(),
|
||||
Some(&["claude:chat".to_string()][..])
|
||||
Some(&["claude:messages".to_string()][..])
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.effective_allowed_models(),
|
||||
|
||||
@@ -41,6 +41,7 @@ impl MinimalCandidateSelectionReadRepository for InMemoryMinimalCandidateSelecti
|
||||
&& row.model_is_available
|
||||
&& api_format_matches(&row.endpoint_api_format, api_format)
|
||||
&& row.key_supports_api_format(api_format)
|
||||
&& key_auth_channel_matches(row, api_format)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
@@ -70,11 +71,42 @@ impl MinimalCandidateSelectionReadRepository for InMemoryMinimalCandidateSelecti
|
||||
}
|
||||
|
||||
fn normalize_api_format(value: &str) -> String {
|
||||
aether_ai_formats::normalize_legacy_openai_format_alias(value)
|
||||
aether_ai_formats::normalize_api_format_alias(value)
|
||||
}
|
||||
|
||||
fn api_format_matches(left: &str, right: &str) -> bool {
|
||||
normalize_api_format(left) == normalize_api_format(right)
|
||||
aether_ai_formats::api_format_alias_matches(left, right)
|
||||
}
|
||||
|
||||
fn key_auth_channel_matches(row: &StoredMinimalCandidateSelectionRow, api_format: &str) -> bool {
|
||||
let provider_type = row.provider_type.trim().to_ascii_lowercase();
|
||||
let auth_type = row.key_auth_type.trim().to_ascii_lowercase();
|
||||
let api_format = normalize_api_format(api_format);
|
||||
match provider_type.as_str() {
|
||||
"codex" => {
|
||||
auth_type == "oauth"
|
||||
&& matches!(
|
||||
api_format.as_str(),
|
||||
"openai:responses" | "openai:responses:compact" | "openai:image"
|
||||
)
|
||||
}
|
||||
"claude_code" => auth_type == "oauth" && api_format == "claude:messages",
|
||||
"kiro" => {
|
||||
matches!(auth_type.as_str(), "oauth" | "bearer") && api_format == "claude:messages"
|
||||
}
|
||||
"gemini_cli" | "antigravity" => {
|
||||
auth_type == "oauth" && api_format == "gemini:generate_content"
|
||||
}
|
||||
"vertex_ai" => {
|
||||
(auth_type == "api_key" && api_format == "gemini:generate_content")
|
||||
|| (matches!(auth_type.as_str(), "service_account" | "vertex_ai")
|
||||
&& matches!(
|
||||
api_format.as_str(),
|
||||
"claude:messages" | "gemini:generate_content"
|
||||
))
|
||||
}
|
||||
_ => auth_type != "oauth",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -65,18 +65,26 @@ WHERE p.is_active = TRUE
|
||||
AND LOWER(pe.api_format) = LOWER($1)
|
||||
AND (
|
||||
pak.api_formats IS NULL
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) IN (
|
||||
'claude_code',
|
||||
'codex',
|
||||
'gemini_cli',
|
||||
'vertex_ai',
|
||||
'antigravity'
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM json_array_elements_text(pak.api_formats) AS fmt(value)
|
||||
WHERE LOWER(BTRIM(fmt.value)) = ANY($2::text[])
|
||||
)
|
||||
)
|
||||
AND (
|
||||
(
|
||||
LOWER(BTRIM(p.provider_type)) = 'codex'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($3) IN ('openai:responses', 'openai:responses:compact', 'openai:image')
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'claude_code'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($3) = 'claude:messages'
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'kiro'
|
||||
AND LOWER($3) = 'claude:messages'
|
||||
AND (
|
||||
LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
OR (
|
||||
@@ -86,10 +94,34 @@ WHERE p.is_active = TRUE
|
||||
)
|
||||
)
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM json_array_elements_text(pak.api_formats) AS fmt(value)
|
||||
WHERE LOWER(fmt.value) = LOWER($1)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) IN ('gemini_cli', 'antigravity')
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($3) = 'gemini:generate_content'
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'vertex_ai'
|
||||
AND (
|
||||
(
|
||||
LOWER(BTRIM(pak.auth_type)) = 'api_key'
|
||||
AND LOWER($3) = 'gemini:generate_content'
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(pak.auth_type)) IN ('service_account', 'vertex_ai')
|
||||
AND LOWER($3) IN ('claude:messages', 'gemini:generate_content')
|
||||
)
|
||||
)
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) NOT IN (
|
||||
'claude_code',
|
||||
'codex',
|
||||
'gemini_cli',
|
||||
'vertex_ai',
|
||||
'antigravity',
|
||||
'kiro'
|
||||
)
|
||||
AND LOWER(BTRIM(pak.auth_type)) <> 'oauth'
|
||||
)
|
||||
)
|
||||
ORDER BY
|
||||
@@ -159,18 +191,26 @@ WHERE p.is_active = TRUE
|
||||
AND gm.name = $2
|
||||
AND (
|
||||
pak.api_formats IS NULL
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) IN (
|
||||
'claude_code',
|
||||
'codex',
|
||||
'gemini_cli',
|
||||
'vertex_ai',
|
||||
'antigravity'
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM json_array_elements_text(pak.api_formats) AS fmt(value)
|
||||
WHERE LOWER(BTRIM(fmt.value)) = ANY($3::text[])
|
||||
)
|
||||
)
|
||||
AND (
|
||||
(
|
||||
LOWER(BTRIM(p.provider_type)) = 'codex'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($4) IN ('openai:responses', 'openai:responses:compact', 'openai:image')
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'claude_code'
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($4) = 'claude:messages'
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'kiro'
|
||||
AND LOWER($4) = 'claude:messages'
|
||||
AND (
|
||||
LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
OR (
|
||||
@@ -180,10 +220,34 @@ WHERE p.is_active = TRUE
|
||||
)
|
||||
)
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM json_array_elements_text(pak.api_formats) AS fmt(value)
|
||||
WHERE LOWER(fmt.value) = LOWER($1)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) IN ('gemini_cli', 'antigravity')
|
||||
AND LOWER(BTRIM(pak.auth_type)) = 'oauth'
|
||||
AND LOWER($4) = 'gemini:generate_content'
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) = 'vertex_ai'
|
||||
AND (
|
||||
(
|
||||
LOWER(BTRIM(pak.auth_type)) = 'api_key'
|
||||
AND LOWER($4) = 'gemini:generate_content'
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(pak.auth_type)) IN ('service_account', 'vertex_ai')
|
||||
AND LOWER($4) IN ('claude:messages', 'gemini:generate_content')
|
||||
)
|
||||
)
|
||||
)
|
||||
OR (
|
||||
LOWER(BTRIM(p.provider_type)) NOT IN (
|
||||
'claude_code',
|
||||
'codex',
|
||||
'gemini_cli',
|
||||
'vertex_ai',
|
||||
'antigravity',
|
||||
'kiro'
|
||||
)
|
||||
AND LOWER(BTRIM(pak.auth_type)) <> 'oauth'
|
||||
)
|
||||
)
|
||||
ORDER BY
|
||||
@@ -228,11 +292,16 @@ impl SqlxMinimalCandidateSelectionReadRepository {
|
||||
api_format: &str,
|
||||
) -> Result<Vec<StoredMinimalCandidateSelectionRow>, DataLayerError> {
|
||||
let mut rows = Vec::new();
|
||||
for api_format in api_format_aliases(api_format) {
|
||||
let canonical_api_format = normalize_api_format(api_format);
|
||||
let storage_aliases = api_format_aliases(&canonical_api_format);
|
||||
let sql_match_aliases = sql_match_aliases(&storage_aliases);
|
||||
for api_format in storage_aliases {
|
||||
rows.extend(
|
||||
Self::collect_query_rows(
|
||||
sqlx::query(LIST_FOR_EXACT_API_FORMAT_SQL)
|
||||
.bind(api_format)
|
||||
.bind(sql_match_aliases.clone())
|
||||
.bind(canonical_api_format.clone())
|
||||
.fetch(&self.pool),
|
||||
map_candidate_selection_row,
|
||||
)
|
||||
@@ -248,12 +317,17 @@ impl SqlxMinimalCandidateSelectionReadRepository {
|
||||
global_model_name: &str,
|
||||
) -> Result<Vec<StoredMinimalCandidateSelectionRow>, DataLayerError> {
|
||||
let mut rows = Vec::new();
|
||||
for api_format in api_format_aliases(api_format) {
|
||||
let canonical_api_format = normalize_api_format(api_format);
|
||||
let storage_aliases = api_format_aliases(&canonical_api_format);
|
||||
let sql_match_aliases = sql_match_aliases(&storage_aliases);
|
||||
for api_format in storage_aliases {
|
||||
rows.extend(
|
||||
Self::collect_query_rows(
|
||||
sqlx::query(LIST_FOR_EXACT_API_FORMAT_AND_GLOBAL_MODEL_SQL)
|
||||
.bind(api_format)
|
||||
.bind(global_model_name)
|
||||
.bind(sql_match_aliases.clone())
|
||||
.bind(canonical_api_format.clone())
|
||||
.fetch(&self.pool),
|
||||
map_candidate_selection_row,
|
||||
)
|
||||
@@ -265,7 +339,18 @@ impl SqlxMinimalCandidateSelectionReadRepository {
|
||||
}
|
||||
|
||||
fn api_format_aliases(api_format: &str) -> Vec<String> {
|
||||
aether_ai_formats::openai_format_storage_aliases(api_format)
|
||||
aether_ai_formats::api_format_storage_aliases(api_format)
|
||||
}
|
||||
|
||||
fn normalize_api_format(api_format: &str) -> String {
|
||||
aether_ai_formats::normalize_api_format_alias(api_format)
|
||||
}
|
||||
|
||||
fn sql_match_aliases(api_formats: &[String]) -> Vec<String> {
|
||||
api_formats
|
||||
.iter()
|
||||
.map(|value| value.trim().to_ascii_lowercase())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn dedupe_candidate_selection_rows(
|
||||
@@ -521,7 +606,13 @@ fn parse_provider_model_mapping_object_lenient(
|
||||
let api_formats = parse_string_list(
|
||||
object.get("api_formats").cloned(),
|
||||
"models.provider_model_mappings.api_formats",
|
||||
)?;
|
||||
)?
|
||||
.map(|formats| {
|
||||
formats
|
||||
.into_iter()
|
||||
.map(|value| aether_ai_formats::normalize_api_format_alias(&value))
|
||||
.collect()
|
||||
});
|
||||
|
||||
Ok(Some(StoredProviderModelMapping {
|
||||
name: name.to_string(),
|
||||
|
||||
@@ -12,8 +12,8 @@ const MODEL_FETCH_FORMAT_PRIORITY: &[&[&str]] = &[
|
||||
"openai:responses",
|
||||
"openai:responses:compact",
|
||||
],
|
||||
&["claude:chat", "claude:cli"],
|
||||
&["gemini:chat", "gemini:cli"],
|
||||
&["claude:messages"],
|
||||
&["gemini:generate_content"],
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -221,10 +221,8 @@ pub fn endpoint_supports_rust_models_fetch(api_format: &str) -> bool {
|
||||
"openai:chat"
|
||||
| "openai:responses"
|
||||
| "openai:responses:compact"
|
||||
| "claude:chat"
|
||||
| "claude:cli"
|
||||
| "gemini:chat"
|
||||
| "gemini:cli"
|
||||
| "claude:messages"
|
||||
| "gemini:generate_content"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -239,25 +237,25 @@ pub fn provider_type_uses_preset_models(provider_type: &str) -> bool {
|
||||
pub fn preset_models_for_provider(provider_type: &str) -> Option<Vec<Value>> {
|
||||
let models = match provider_type.trim().to_ascii_lowercase().as_str() {
|
||||
"gemini_cli" => vec![
|
||||
preset_model("gemini-2.5-pro", "google", "Gemini 2.5 Pro", "gemini:cli"),
|
||||
preset_model("gemini-2.5-flash", "google", "Gemini 2.5 Flash", "gemini:cli"),
|
||||
preset_model("gemini-3-pro-preview", "google", "Gemini 3 Pro Preview", "gemini:cli"),
|
||||
preset_model("gemini-3-flash-preview", "google", "Gemini 3 Flash Preview", "gemini:cli"),
|
||||
preset_model("gemini-3.1-pro-preview", "google", "Gemini 3.1 Pro Preview", "gemini:cli"),
|
||||
preset_model("gemini-2.5-pro", "google", "Gemini 2.5 Pro", "gemini:generate_content"),
|
||||
preset_model("gemini-2.5-flash", "google", "Gemini 2.5 Flash", "gemini:generate_content"),
|
||||
preset_model("gemini-3-pro-preview", "google", "Gemini 3 Pro Preview", "gemini:generate_content"),
|
||||
preset_model("gemini-3-flash-preview", "google", "Gemini 3 Flash Preview", "gemini:generate_content"),
|
||||
preset_model("gemini-3.1-pro-preview", "google", "Gemini 3.1 Pro Preview", "gemini:generate_content"),
|
||||
],
|
||||
"kiro" => vec![
|
||||
preset_model("claude-sonnet-4.5", "anthropic", "Claude Sonnet 4.5", "claude:cli"),
|
||||
preset_model("claude-sonnet-4.6", "anthropic", "Claude Sonnet 4.6", "claude:cli"),
|
||||
preset_model("claude-opus-4.5", "anthropic", "Claude Opus 4.5", "claude:cli"),
|
||||
preset_model("claude-opus-4.6", "anthropic", "Claude Opus 4.6", "claude:cli"),
|
||||
preset_model("claude-haiku-4.5", "anthropic", "Claude Haiku 4.5", "claude:cli"),
|
||||
preset_model("claude-sonnet-4.5", "anthropic", "Claude Sonnet 4.5", "claude:messages"),
|
||||
preset_model("claude-sonnet-4.6", "anthropic", "Claude Sonnet 4.6", "claude:messages"),
|
||||
preset_model("claude-opus-4.5", "anthropic", "Claude Opus 4.5", "claude:messages"),
|
||||
preset_model("claude-opus-4.6", "anthropic", "Claude Opus 4.6", "claude:messages"),
|
||||
preset_model("claude-haiku-4.5", "anthropic", "Claude Haiku 4.5", "claude:messages"),
|
||||
],
|
||||
"claude_code" => vec![
|
||||
preset_model("claude-opus-4-5-20251101", "anthropic", "Claude Opus 4.5", "claude:cli"),
|
||||
preset_model("claude-opus-4-6", "anthropic", "Claude Opus 4.6", "claude:cli"),
|
||||
preset_model("claude-sonnet-4-6", "anthropic", "Claude Sonnet 4.6", "claude:cli"),
|
||||
preset_model("claude-sonnet-4-5-20250929", "anthropic", "Claude Sonnet 4.5", "claude:cli"),
|
||||
preset_model("claude-haiku-4-5-20251001", "anthropic", "Claude Haiku 4.5", "claude:cli"),
|
||||
preset_model("claude-opus-4-5-20251101", "anthropic", "Claude Opus 4.5", "claude:messages"),
|
||||
preset_model("claude-opus-4-6", "anthropic", "Claude Opus 4.6", "claude:messages"),
|
||||
preset_model("claude-sonnet-4-6", "anthropic", "Claude Sonnet 4.6", "claude:messages"),
|
||||
preset_model("claude-sonnet-4-5-20250929", "anthropic", "Claude Sonnet 4.5", "claude:messages"),
|
||||
preset_model("claude-haiku-4-5-20251001", "anthropic", "Claude Haiku 4.5", "claude:messages"),
|
||||
],
|
||||
"codex" => vec![
|
||||
preset_model("gpt-5", "openai", "GPT-5", "openai:responses"),
|
||||
@@ -570,7 +568,7 @@ fn wildcard_matches(pattern: &str, model_id: &str) -> bool {
|
||||
}
|
||||
|
||||
fn normalize_api_format(value: &str) -> String {
|
||||
aether_ai_formats::normalize_legacy_openai_format_alias(value)
|
||||
aether_ai_formats::normalize_api_format_alias(value)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -726,7 +724,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_models_response_page_reads_claude_pagination_state() {
|
||||
let parsed = parse_models_response_page(
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
&json!({
|
||||
"data": [{"id": "claude-sonnet-4"}],
|
||||
"has_more": true,
|
||||
@@ -750,8 +748,8 @@ mod tests {
|
||||
),
|
||||
sample_endpoint(
|
||||
"provider-1",
|
||||
"endpoint-cli",
|
||||
"openai:cli",
|
||||
"endpoint-compact",
|
||||
"openai:responses:compact",
|
||||
"https://example.com",
|
||||
),
|
||||
sample_endpoint(
|
||||
@@ -769,8 +767,8 @@ mod tests {
|
||||
let endpoints = vec![
|
||||
sample_endpoint(
|
||||
"provider-1",
|
||||
"endpoint-cli",
|
||||
"openai:cli",
|
||||
"endpoint-compact",
|
||||
"openai:responses:compact",
|
||||
"https://example.com",
|
||||
),
|
||||
sample_endpoint(
|
||||
|
||||
@@ -291,7 +291,7 @@ async fn fetch_vertex_api_key_models(
|
||||
&url,
|
||||
auth_config,
|
||||
"google",
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -362,8 +362,8 @@ async fn fetch_vertex_service_account_models(
|
||||
format!("https://{region}-aiplatform.googleapis.com")
|
||||
};
|
||||
for (publisher, transport, api_format) in [
|
||||
("google", gemini_transport, "gemini:chat"),
|
||||
("anthropic", claude_transport, "claude:chat"),
|
||||
("google", gemini_transport, "gemini:generate_content"),
|
||||
("anthropic", claude_transport, "claude:messages"),
|
||||
] {
|
||||
let url =
|
||||
build_vertex_service_account_list_url(&base, &project_id, ®ion, publisher, None);
|
||||
@@ -517,7 +517,7 @@ async fn exchange_vertex_service_account_token(
|
||||
body_ref: None,
|
||||
},
|
||||
stream: false,
|
||||
client_api_format: "gemini:chat".to_string(),
|
||||
client_api_format: "gemini:generate_content".to_string(),
|
||||
provider_api_format: "vertex_ai:service_account_token".to_string(),
|
||||
model_name: Some("token".to_string()),
|
||||
proxy: runtime.resolve_model_fetch_proxy(transport).await,
|
||||
@@ -618,7 +618,7 @@ fn parse_antigravity_models_response(body: &Value) -> Result<(Vec<Value>, Option
|
||||
"object": "model",
|
||||
"owned_by": "antigravity",
|
||||
"display_name": display_name,
|
||||
"api_formats": ["gemini:chat"],
|
||||
"api_formats": ["gemini:generate_content"],
|
||||
}));
|
||||
|
||||
let quota_payload = build_antigravity_quota_payload(model_object.get("quotaInfo"));
|
||||
@@ -844,7 +844,9 @@ fn vertex_effective_format(model_id: &str, auth_config: Option<&Value>) -> Strin
|
||||
&& model_id.starts_with(prefix)
|
||||
&& api_format.as_str().is_some()
|
||||
{
|
||||
return normalize_api_format(api_format.as_str().unwrap_or("gemini:chat"));
|
||||
return normalize_api_format(
|
||||
api_format.as_str().unwrap_or("gemini:generate_content"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -856,9 +858,9 @@ fn vertex_effective_format(model_id: &str, auth_config: Option<&Value>) -> Strin
|
||||
}
|
||||
}
|
||||
if model_id.starts_with("claude-") {
|
||||
"claude:chat".to_string()
|
||||
"claude:messages".to_string()
|
||||
} else {
|
||||
"gemini:chat".to_string()
|
||||
"gemini:generate_content".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1150,9 +1152,9 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "gemini:cli".to_string(),
|
||||
api_format: "gemini:generate_content".to_string(),
|
||||
api_family: Some("gemini".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
endpoint_kind: Some("generate_content".to_string()),
|
||||
is_active: true,
|
||||
base_url: "https://aiplatform.googleapis.com".to_string(),
|
||||
header_rules: None,
|
||||
@@ -1169,7 +1171,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "api_key".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["gemini:cli".to_string()]),
|
||||
api_formats: Some(vec!["gemini:generate_content".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
@@ -1204,7 +1206,7 @@ mod tests {
|
||||
assert_eq!(outcome.cached_models.len(), 1);
|
||||
assert_eq!(
|
||||
outcome.cached_models[0]["api_formats"][0].as_str(),
|
||||
Some("gemini:chat")
|
||||
Some("gemini:generate_content")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ pub async fn build_standard_models_fetch_execution_plan(
|
||||
) -> Result<ExecutionPlan, String> {
|
||||
let api_format = transport.endpoint.api_format.trim().to_ascii_lowercase();
|
||||
let provider_api_format = api_format.clone();
|
||||
let mut headers = standard_models_fetch_headers(&api_format);
|
||||
let mut headers = standard_models_fetch_headers(&api_format, &transport.provider.provider_type);
|
||||
let mut protected_headers = Vec::<String>::new();
|
||||
|
||||
if api_format.starts_with("openai:") || api_format.starts_with("claude:") {
|
||||
@@ -167,7 +167,7 @@ pub async fn build_antigravity_fetch_available_models_plan(
|
||||
headers,
|
||||
content_type: Some("application/json".to_string()),
|
||||
body: RequestBody::from_json(json!({ "project": project_id })),
|
||||
client_api_format: "gemini:chat".to_string(),
|
||||
client_api_format: "gemini:generate_content".to_string(),
|
||||
provider_api_format: ANTIGRAVITY_FETCH_PROVIDER_API_FORMAT.to_string(),
|
||||
model_name: Some("fetchAvailableModels".to_string()),
|
||||
},
|
||||
@@ -207,7 +207,7 @@ pub async fn build_gemini_cli_load_code_assist_plan(
|
||||
"pluginType": "GEMINI",
|
||||
}
|
||||
})),
|
||||
client_api_format: "gemini:cli".to_string(),
|
||||
client_api_format: "gemini:generate_content".to_string(),
|
||||
provider_api_format: GEMINI_CLI_LOAD_CODE_ASSIST_PROVIDER_API_FORMAT.to_string(),
|
||||
model_name: Some("loadCodeAssist".to_string()),
|
||||
},
|
||||
@@ -222,7 +222,7 @@ pub async fn build_vertex_models_fetch_execution_plan(
|
||||
api_format: &str,
|
||||
auth_header: Option<(String, String)>,
|
||||
) -> Result<ExecutionPlan, String> {
|
||||
let mut headers = standard_models_fetch_headers(api_format);
|
||||
let mut headers = standard_models_fetch_headers(api_format, &transport.provider.provider_type);
|
||||
let mut protected_headers = Vec::<String>::new();
|
||||
if let Some((name, value)) = auth_header {
|
||||
protected_headers.push(name.clone());
|
||||
@@ -380,34 +380,35 @@ fn apply_fetch_header_rules(
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
fn standard_models_fetch_headers(api_format: &str) -> BTreeMap<String, String> {
|
||||
let api_format = aether_ai_formats::normalize_legacy_openai_format_alias(api_format);
|
||||
fn standard_models_fetch_headers(
|
||||
api_format: &str,
|
||||
provider_type: &str,
|
||||
) -> BTreeMap<String, String> {
|
||||
let api_format = aether_ai_formats::normalize_api_format_alias(api_format);
|
||||
let provider_type = provider_type.trim().to_ascii_lowercase();
|
||||
match api_format.as_str() {
|
||||
"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(),
|
||||
)]),
|
||||
"claude:cli" => BTreeMap::from([
|
||||
("user-agent".to_string(), CLAUDE_CLI_USER_AGENT.to_string()),
|
||||
(
|
||||
"claude:messages" => {
|
||||
let mut headers = BTreeMap::from([(
|
||||
"anthropic-version".to_string(),
|
||||
CLAUDE_VERSION_HEADER.to_string(),
|
||||
),
|
||||
]),
|
||||
"gemini:chat" => BROWSER_FINGERPRINT_HEADERS
|
||||
.iter()
|
||||
.map(|(key, value)| (key.to_string(), value.to_string()))
|
||||
.collect(),
|
||||
"gemini:cli" => {
|
||||
)]);
|
||||
if matches!(provider_type.as_str(), "claude_code" | "kiro") {
|
||||
headers.insert("user-agent".to_string(), CLAUDE_CLI_USER_AGENT.to_string());
|
||||
}
|
||||
headers
|
||||
}
|
||||
"gemini:generate_content" => {
|
||||
let mut headers = BROWSER_FINGERPRINT_HEADERS
|
||||
.iter()
|
||||
.map(|(key, value)| (key.to_string(), value.to_string()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
headers.insert("user-agent".to_string(), GEMINI_CLI_USER_AGENT.to_string());
|
||||
if provider_type == "gemini_cli" {
|
||||
headers.insert("user-agent".to_string(), GEMINI_CLI_USER_AGENT.to_string());
|
||||
}
|
||||
headers
|
||||
}
|
||||
_ => BTreeMap::new(),
|
||||
@@ -625,7 +626,7 @@ mod tests {
|
||||
oauth_auth: None,
|
||||
proxy: None,
|
||||
};
|
||||
let mut transport = sample_transport("custom", "claude:chat", "api_key");
|
||||
let mut transport = sample_transport("custom", "claude:messages", "api_key");
|
||||
transport.key.decrypted_auth_config = None;
|
||||
let plan =
|
||||
build_standard_models_fetch_execution_plan(&runtime, &transport, Some("cursor-1"))
|
||||
@@ -652,7 +653,7 @@ mod tests {
|
||||
oauth_auth: None,
|
||||
proxy: None,
|
||||
};
|
||||
let mut transport = sample_transport("custom", "gemini:chat", "api_key");
|
||||
let mut transport = sample_transport("custom", "gemini:generate_content", "api_key");
|
||||
transport.key.decrypted_auth_config = None;
|
||||
let plan = build_models_fetch_execution_plan(&runtime, &transport)
|
||||
.await
|
||||
@@ -677,7 +678,7 @@ mod tests {
|
||||
),
|
||||
proxy: None,
|
||||
};
|
||||
let transport = sample_transport("antigravity", "gemini:chat", "oauth");
|
||||
let transport = sample_transport("antigravity", "gemini:generate_content", "oauth");
|
||||
let plan = build_antigravity_fetch_available_models_plan(
|
||||
&runtime,
|
||||
&transport,
|
||||
@@ -716,7 +717,7 @@ mod tests {
|
||||
),
|
||||
proxy: None,
|
||||
};
|
||||
let transport = sample_transport("gemini_cli", "gemini:cli", "oauth");
|
||||
let transport = sample_transport("gemini_cli", "gemini:generate_content", "oauth");
|
||||
let plan = build_gemini_cli_load_code_assist_plan(&runtime, &transport)
|
||||
.await
|
||||
.expect("plan");
|
||||
@@ -738,13 +739,13 @@ mod tests {
|
||||
oauth_auth: None,
|
||||
proxy: None,
|
||||
};
|
||||
let mut transport = sample_transport("vertex_ai", "claude:chat", "api_key");
|
||||
let mut transport = sample_transport("vertex_ai", "claude:messages", "api_key");
|
||||
transport.key.decrypted_auth_config = None;
|
||||
let plan = build_vertex_models_fetch_execution_plan(
|
||||
&runtime,
|
||||
&transport,
|
||||
"https://aiplatform.googleapis.com/v1/publishers/google/models?key=secret",
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -56,10 +56,9 @@ pub fn classify_local_antigravity_request_support(
|
||||
);
|
||||
}
|
||||
|
||||
let endpoint_format = transport.endpoint.api_format.trim();
|
||||
if !endpoint_format.eq_ignore_ascii_case("gemini:chat")
|
||||
&& !endpoint_format.eq_ignore_ascii_case("gemini:cli")
|
||||
{
|
||||
let endpoint_format =
|
||||
aether_ai_formats::normalize_api_format_alias(&transport.endpoint.api_format);
|
||||
if endpoint_format != "gemini:generate_content" {
|
||||
return AntigravityRequestSideSupport::Unsupported(
|
||||
AntigravityRequestSideUnsupportedReason::UnsupportedApiFormat,
|
||||
);
|
||||
|
||||
@@ -302,7 +302,7 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "claude:chat".to_string(),
|
||||
api_format: "claude:messages".to_string(),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_kind: Some("chat".to_string()),
|
||||
is_active: true,
|
||||
|
||||
@@ -99,7 +99,7 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "claude:cli".to_string(),
|
||||
api_format: "claude:messages".to_string(),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
is_active: true,
|
||||
@@ -118,7 +118,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "bearer".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["claude:cli".to_string()]),
|
||||
api_formats: Some(vec!["claude:messages".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
@@ -136,7 +136,7 @@ mod tests {
|
||||
fn supports_claude_code_transport_when_auth_and_tls_are_valid() {
|
||||
assert!(supports_local_claude_code_transport_with_network(
|
||||
&sample_transport(),
|
||||
"claude:cli"
|
||||
"claude:messages"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -147,7 +147,10 @@ mod tests {
|
||||
transport.key.decrypted_api_key = "__placeholder__".to_string();
|
||||
|
||||
assert_eq!(
|
||||
local_claude_code_transport_unsupported_reason_with_network(&transport, "claude:cli"),
|
||||
local_claude_code_transport_unsupported_reason_with_network(
|
||||
&transport,
|
||||
"claude:messages"
|
||||
),
|
||||
Some("transport_auth_unavailable")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "claude:cli".to_string(),
|
||||
api_format: "claude:messages".to_string(),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
is_active: true,
|
||||
@@ -195,7 +195,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "bearer".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["claude:cli".to_string()]),
|
||||
api_formats: Some(vec!["claude:messages".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
|
||||
@@ -25,12 +25,10 @@ pub fn local_kiro_request_transport_unsupported_reason_with_network(
|
||||
{
|
||||
return Some("transport_provider_type_unsupported");
|
||||
}
|
||||
if !transport
|
||||
.endpoint
|
||||
.api_format
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("claude:cli")
|
||||
{
|
||||
if !aether_ai_formats::api_format_alias_matches(
|
||||
&transport.endpoint.api_format,
|
||||
"claude:messages",
|
||||
) {
|
||||
return Some("transport_api_format_mismatch");
|
||||
}
|
||||
if !header_rules_are_locally_supported(transport.endpoint.header_rules.as_ref()) {
|
||||
@@ -69,12 +67,10 @@ pub fn supports_local_kiro_request_transport(transport: &GatewayProviderTranspor
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if !transport
|
||||
.endpoint
|
||||
.api_format
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("claude:cli")
|
||||
{
|
||||
if !aether_ai_formats::api_format_alias_matches(
|
||||
&transport.endpoint.api_format,
|
||||
"claude:messages",
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
supports_local_kiro_request_shape(
|
||||
@@ -120,9 +116,9 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "claude:cli".to_string(),
|
||||
api_format: "claude:messages".to_string(),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
endpoint_kind: Some("messages".to_string()),
|
||||
is_active: true,
|
||||
base_url: "https://kiro.example".to_string(),
|
||||
header_rules: None,
|
||||
@@ -139,7 +135,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "bearer".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["claude:cli".to_string()]),
|
||||
api_formats: Some(vec!["claude:messages".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
|
||||
@@ -212,7 +212,7 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "claude:cli".to_string(),
|
||||
api_format: "claude:messages".to_string(),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
is_active: true,
|
||||
@@ -231,7 +231,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "bearer".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["claude:cli".to_string()]),
|
||||
api_formats: Some(vec!["claude:messages".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
|
||||
@@ -651,7 +651,7 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "claude:cli".to_string(),
|
||||
api_format: "claude:messages".to_string(),
|
||||
api_family: Some("claude".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
is_active: true,
|
||||
|
||||
@@ -200,5 +200,5 @@ fn local_same_format_transport_unsupported_reason(
|
||||
}
|
||||
|
||||
fn same_api_format(left: &str, right: &str) -> bool {
|
||||
aether_ai_formats::legacy_openai_format_alias_matches(left, right)
|
||||
aether_ai_formats::api_format_alias_matches(left, right)
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ const CLAUDE_CODE_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProvider
|
||||
version: 1,
|
||||
base_url: "https://api.anthropic.com",
|
||||
endpoints: &[FixedProviderEndpointTemplate {
|
||||
item_key: "claude:cli",
|
||||
api_format: "claude:cli",
|
||||
item_key: "claude:messages",
|
||||
api_format: "claude:messages",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
}],
|
||||
@@ -100,8 +100,8 @@ const KIRO_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTemplat
|
||||
version: 1,
|
||||
base_url: "https://q.{region}.amazonaws.com",
|
||||
endpoints: &[FixedProviderEndpointTemplate {
|
||||
item_key: "claude:cli",
|
||||
api_format: "claude:cli",
|
||||
item_key: "claude:messages",
|
||||
api_format: "claude:messages",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
}],
|
||||
@@ -112,8 +112,8 @@ const GEMINI_CLI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderT
|
||||
version: 1,
|
||||
base_url: "https://cloudcode-pa.googleapis.com",
|
||||
endpoints: &[FixedProviderEndpointTemplate {
|
||||
item_key: "gemini:cli",
|
||||
api_format: "gemini:cli",
|
||||
item_key: "gemini:generate_content",
|
||||
api_format: "gemini:generate_content",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
}],
|
||||
@@ -125,14 +125,14 @@ const VERTEX_AI_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTe
|
||||
base_url: "https://aiplatform.googleapis.com",
|
||||
endpoints: &[
|
||||
FixedProviderEndpointTemplate {
|
||||
item_key: "gemini:chat",
|
||||
api_format: "gemini:chat",
|
||||
item_key: "gemini:generate_content",
|
||||
api_format: "gemini:generate_content",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
},
|
||||
FixedProviderEndpointTemplate {
|
||||
item_key: "claude:chat",
|
||||
api_format: "claude:chat",
|
||||
item_key: "claude:messages",
|
||||
api_format: "claude:messages",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
},
|
||||
@@ -144,8 +144,8 @@ const ANTIGRAVITY_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProvider
|
||||
version: 1,
|
||||
base_url: "https://cloudcode-pa.googleapis.com",
|
||||
endpoints: &[FixedProviderEndpointTemplate {
|
||||
item_key: "gemini:chat",
|
||||
api_format: "gemini:chat",
|
||||
item_key: "gemini:generate_content",
|
||||
api_format: "gemini:generate_content",
|
||||
custom_path: None,
|
||||
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
|
||||
}],
|
||||
@@ -181,7 +181,7 @@ pub fn fixed_provider_endpoint_template_by_api_format(
|
||||
provider_type: &str,
|
||||
api_format: &str,
|
||||
) -> Option<&'static FixedProviderEndpointTemplate> {
|
||||
let normalized = aether_ai_formats::normalize_legacy_openai_format_alias(api_format);
|
||||
let normalized = aether_ai_formats::normalize_api_format_alias(api_format);
|
||||
fixed_provider_template(provider_type)?
|
||||
.endpoints
|
||||
.iter()
|
||||
|
||||
@@ -60,9 +60,7 @@ pub fn build_transport_request_url(
|
||||
));
|
||||
}
|
||||
|
||||
let url = match aether_ai_formats::normalize_legacy_openai_format_alias(&provider_api_format)
|
||||
.as_str()
|
||||
{
|
||||
let url = match aether_ai_formats::normalize_api_format_alias(&provider_api_format).as_str() {
|
||||
"openai:chat" => Some(build_openai_chat_url(
|
||||
&transport.endpoint.base_url,
|
||||
params.request_query,
|
||||
@@ -77,11 +75,11 @@ pub fn build_transport_request_url(
|
||||
params.request_query,
|
||||
true,
|
||||
)),
|
||||
"claude:chat" | "claude:cli" => Some(build_claude_messages_url(
|
||||
"claude:messages" => Some(build_claude_messages_url(
|
||||
&transport.endpoint.base_url,
|
||||
params.request_query,
|
||||
)),
|
||||
"gemini:chat" | "gemini:cli" => build_gemini_content_url(
|
||||
"gemini:generate_content" => build_gemini_content_url(
|
||||
&transport.endpoint.base_url,
|
||||
params.mapped_model?,
|
||||
params.upstream_is_stream,
|
||||
@@ -325,7 +323,7 @@ mod tests {
|
||||
fn uses_vertex_hook_before_custom_path_for_custom_aiplatform_transport() {
|
||||
let transport = sample_transport(
|
||||
"custom",
|
||||
"gemini:cli",
|
||||
"gemini:generate_content",
|
||||
"https://aiplatform.googleapis.com",
|
||||
Some("/custom/{model}:{action}"),
|
||||
);
|
||||
@@ -333,7 +331,7 @@ mod tests {
|
||||
let url = build_transport_request_url(
|
||||
&transport,
|
||||
TransportRequestUrlParams {
|
||||
provider_api_format: "gemini:cli",
|
||||
provider_api_format: "gemini:generate_content",
|
||||
mapped_model: Some("gemini-3.1-pro-preview"),
|
||||
upstream_is_stream: true,
|
||||
request_query: Some("foo=bar"),
|
||||
@@ -376,7 +374,7 @@ mod tests {
|
||||
fn expands_custom_path_templates_when_hook_does_not_apply() {
|
||||
let transport = sample_transport(
|
||||
"custom",
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
"https://generativelanguage.googleapis.com",
|
||||
Some("/v1beta/models/{model}:{action}"),
|
||||
);
|
||||
@@ -384,7 +382,7 @@ mod tests {
|
||||
let url = build_transport_request_url(
|
||||
&transport,
|
||||
TransportRequestUrlParams {
|
||||
provider_api_format: "gemini:chat",
|
||||
provider_api_format: "gemini:generate_content",
|
||||
mapped_model: Some("gemini-2.5-pro"),
|
||||
upstream_is_stream: false,
|
||||
request_query: Some("key=client-key&foo=bar"),
|
||||
@@ -403,7 +401,7 @@ mod tests {
|
||||
fn keeps_original_custom_path_when_template_params_are_missing() {
|
||||
let transport = sample_transport(
|
||||
"custom",
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
"https://api.example.com",
|
||||
Some("/v1/messages/{model}"),
|
||||
);
|
||||
@@ -411,7 +409,7 @@ mod tests {
|
||||
let url = build_transport_request_url(
|
||||
&transport,
|
||||
TransportRequestUrlParams {
|
||||
provider_api_format: "claude:chat",
|
||||
provider_api_format: "claude:messages",
|
||||
mapped_model: None,
|
||||
upstream_is_stream: false,
|
||||
request_query: None,
|
||||
|
||||
@@ -750,7 +750,7 @@ mod tests {
|
||||
let endpoint = StoredProviderCatalogEndpoint::new(
|
||||
"endpoint-safe-3".to_string(),
|
||||
"provider-1".to_string(),
|
||||
"gemini:chat".to_string(),
|
||||
"gemini:generate_content".to_string(),
|
||||
Some("gemini".to_string()),
|
||||
Some("chat".to_string()),
|
||||
true,
|
||||
@@ -785,7 +785,7 @@ mod tests {
|
||||
)
|
||||
.expect("key should build")
|
||||
.with_transport_fields(
|
||||
Some(serde_json::json!(["gemini:chat"])),
|
||||
Some(serde_json::json!(["gemini:generate_content"])),
|
||||
encrypted_api_key,
|
||||
Some(encrypted_auth_config),
|
||||
None,
|
||||
|
||||
@@ -68,7 +68,7 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "gemini:chat".to_string(),
|
||||
api_format: "gemini:generate_content".to_string(),
|
||||
api_family: Some("gemini".to_string()),
|
||||
endpoint_kind: Some("chat".to_string()),
|
||||
is_active: true,
|
||||
@@ -87,7 +87,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "api_key".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["gemini:chat".to_string()]),
|
||||
api_formats: Some(vec!["gemini:generate_content".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
@@ -127,7 +127,7 @@ mod tests {
|
||||
fn resolves_query_auth_for_custom_aiplatform_transport() {
|
||||
let mut transport = sample_transport();
|
||||
transport.provider.provider_type = "custom".to_string();
|
||||
transport.endpoint.api_format = "gemini:cli".to_string();
|
||||
transport.endpoint.api_format = "gemini:generate_content".to_string();
|
||||
|
||||
let auth = resolve_local_vertex_api_key_query_auth(&transport)
|
||||
.expect("custom aiplatform transport should resolve");
|
||||
|
||||
@@ -97,7 +97,7 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "gemini:cli".to_string(),
|
||||
api_format: "gemini:generate_content".to_string(),
|
||||
api_family: Some("gemini".to_string()),
|
||||
endpoint_kind: Some("cli".to_string()),
|
||||
is_active: true,
|
||||
@@ -116,7 +116,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "api_key".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["gemini:cli".to_string()]),
|
||||
api_formats: Some(vec!["gemini:generate_content".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
@@ -156,8 +156,13 @@ mod tests {
|
||||
#[test]
|
||||
fn detects_vertex_query_auth_usage_for_gemini_formats() {
|
||||
let transport = sample_transport();
|
||||
assert!(uses_vertex_api_key_query_auth(&transport, "gemini:cli"));
|
||||
assert!(uses_vertex_api_key_query_auth(&transport, "gemini:chat"));
|
||||
assert!(!uses_vertex_api_key_query_auth(&transport, "claude:chat"));
|
||||
assert!(uses_vertex_api_key_query_auth(
|
||||
&transport,
|
||||
"gemini:generate_content"
|
||||
));
|
||||
assert!(!uses_vertex_api_key_query_auth(
|
||||
&transport,
|
||||
"claude:messages"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,8 @@ pub fn local_vertex_api_key_gemini_transport_unsupported_reason_with_network(
|
||||
Some("key_inactive")
|
||||
};
|
||||
}
|
||||
if !transport
|
||||
.endpoint
|
||||
.api_format
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("gemini:chat")
|
||||
&& !transport
|
||||
.endpoint
|
||||
.api_format
|
||||
.trim()
|
||||
.eq_ignore_ascii_case("gemini:cli")
|
||||
if aether_ai_formats::normalize_api_format_alias(&transport.endpoint.api_format)
|
||||
!= "gemini:generate_content"
|
||||
{
|
||||
return Some("transport_api_format_mismatch");
|
||||
}
|
||||
@@ -66,7 +58,7 @@ pub fn supports_local_vertex_api_key_gemini_transport(
|
||||
) -> bool {
|
||||
supports_local_vertex_api_key_same_format_transport(
|
||||
transport,
|
||||
&["gemini:chat", "gemini:cli"],
|
||||
&["gemini:generate_content"],
|
||||
false,
|
||||
)
|
||||
}
|
||||
@@ -80,13 +72,21 @@ pub fn supports_local_vertex_api_key_gemini_transport_with_network(
|
||||
pub fn supports_local_vertex_api_key_imagen_transport(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
) -> bool {
|
||||
supports_local_vertex_api_key_same_format_transport(transport, &["gemini:chat"], false)
|
||||
supports_local_vertex_api_key_same_format_transport(
|
||||
transport,
|
||||
&["gemini:generate_content"],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn supports_local_vertex_api_key_imagen_transport_with_network(
|
||||
transport: &GatewayProviderTransportSnapshot,
|
||||
) -> bool {
|
||||
supports_local_vertex_api_key_same_format_transport(transport, &["gemini:chat"], true)
|
||||
supports_local_vertex_api_key_same_format_transport(
|
||||
transport,
|
||||
&["gemini:generate_content"],
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
fn supports_local_vertex_api_key_same_format_transport(
|
||||
@@ -97,18 +97,11 @@ fn supports_local_vertex_api_key_same_format_transport(
|
||||
if !transport.provider.is_active || !transport.endpoint.is_active || !transport.key.is_active {
|
||||
return false;
|
||||
}
|
||||
if !transport
|
||||
.endpoint
|
||||
.api_format
|
||||
.trim()
|
||||
.eq_ignore_ascii_case(api_formats[0])
|
||||
&& !api_formats.iter().any(|api_format| {
|
||||
transport
|
||||
.endpoint
|
||||
.api_format
|
||||
.trim()
|
||||
.eq_ignore_ascii_case(api_format)
|
||||
})
|
||||
let endpoint_api_format =
|
||||
aether_ai_formats::normalize_api_format_alias(&transport.endpoint.api_format);
|
||||
if !api_formats
|
||||
.iter()
|
||||
.any(|api_format| endpoint_api_format.eq_ignore_ascii_case(api_format))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -190,9 +183,9 @@ mod tests {
|
||||
endpoint: GatewayProviderTransportEndpoint {
|
||||
id: "endpoint-1".to_string(),
|
||||
provider_id: "provider-1".to_string(),
|
||||
api_format: "gemini:chat".to_string(),
|
||||
api_format: "gemini:generate_content".to_string(),
|
||||
api_family: Some("gemini".to_string()),
|
||||
endpoint_kind: Some("chat".to_string()),
|
||||
endpoint_kind: Some("generate_content".to_string()),
|
||||
is_active: true,
|
||||
base_url: "https://aiplatform.googleapis.com".to_string(),
|
||||
header_rules: None,
|
||||
@@ -209,7 +202,7 @@ mod tests {
|
||||
name: "key".to_string(),
|
||||
auth_type: "api_key".to_string(),
|
||||
is_active: true,
|
||||
api_formats: Some(vec!["gemini:chat".to_string()]),
|
||||
api_formats: Some(vec!["gemini:generate_content".to_string()]),
|
||||
allowed_models: None,
|
||||
capabilities: None,
|
||||
rate_multipliers: None,
|
||||
@@ -231,17 +224,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_vertex_api_key_gemini_cli_subset() {
|
||||
fn supports_vertex_api_key_gemini_generate_content_subset() {
|
||||
let mut transport = sample_transport();
|
||||
transport.endpoint.api_format = "gemini:cli".to_string();
|
||||
transport.endpoint.api_format = "gemini:generate_content".to_string();
|
||||
assert!(supports_local_vertex_api_key_gemini_transport(&transport));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_custom_aiplatform_gemini_cli_subset() {
|
||||
fn supports_custom_aiplatform_gemini_generate_content_subset() {
|
||||
let mut transport = sample_transport();
|
||||
transport.provider.provider_type = "custom".to_string();
|
||||
transport.endpoint.api_format = "gemini:cli".to_string();
|
||||
transport.endpoint.api_format = "gemini:generate_content".to_string();
|
||||
assert!(supports_local_vertex_api_key_gemini_transport(&transport));
|
||||
}
|
||||
|
||||
|
||||
@@ -1090,14 +1090,14 @@ mod tests {
|
||||
Some(serde_json::json!({
|
||||
"openai:chat": {"health_score": 0.85},
|
||||
"openai:responses": {"health_score": 0.45},
|
||||
"claude:chat": {"health_score": 0.70}
|
||||
"claude:messages": {"health_score": 0.70}
|
||||
})),
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(aggregate_provider_key_health_score(&key), Some(0.45));
|
||||
assert_eq!(
|
||||
effective_provider_key_health_score(&key, "gemini:chat"),
|
||||
effective_provider_key_health_score(&key, "gemini:generate_content"),
|
||||
Some(0.45)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ pub fn extract_global_priority_for_format(
|
||||
}
|
||||
|
||||
pub fn normalize_api_format(value: &str) -> String {
|
||||
aether_ai_formats::normalize_legacy_openai_format_alias(value)
|
||||
aether_ai_formats::normalize_api_format_alias(value)
|
||||
}
|
||||
|
||||
fn row_has_candidate_model_name(
|
||||
|
||||
@@ -103,16 +103,16 @@ pub fn infer_internal_finalize_signature(payload: &GatewaySyncReportRequest) ->
|
||||
return Some("openai:video".to_string());
|
||||
}
|
||||
if report_kind.starts_with("claude_chat_") {
|
||||
return Some("claude:chat".to_string());
|
||||
return Some("claude:messages".to_string());
|
||||
}
|
||||
if report_kind.starts_with("claude_cli_") {
|
||||
return Some("claude:cli".to_string());
|
||||
return Some("claude:messages".to_string());
|
||||
}
|
||||
if report_kind.starts_with("gemini_chat_") {
|
||||
return Some("gemini:chat".to_string());
|
||||
return Some("gemini:generate_content".to_string());
|
||||
}
|
||||
if report_kind.starts_with("gemini_cli_") {
|
||||
return Some("gemini:cli".to_string());
|
||||
return Some("gemini:generate_content".to_string());
|
||||
}
|
||||
if report_kind.starts_with("gemini_video_") {
|
||||
return Some("gemini:video".to_string());
|
||||
@@ -121,7 +121,7 @@ pub fn infer_internal_finalize_signature(payload: &GatewaySyncReportRequest) ->
|
||||
}
|
||||
|
||||
pub fn resolve_internal_finalize_route(signature: &str) -> Option<InternalFinalizeRoute> {
|
||||
match aether_ai_formats::normalize_legacy_openai_format_alias(signature).as_str() {
|
||||
match aether_ai_formats::normalize_api_format_alias(signature).as_str() {
|
||||
"openai:chat" => Some(InternalFinalizeRoute {
|
||||
public_path: "/v1/chat/completions",
|
||||
route_family: "openai",
|
||||
@@ -147,25 +147,15 @@ pub fn resolve_internal_finalize_route(signature: &str) -> Option<InternalFinali
|
||||
route_family: "openai",
|
||||
route_kind: "video",
|
||||
}),
|
||||
"claude:chat" => Some(InternalFinalizeRoute {
|
||||
"claude:messages" => Some(InternalFinalizeRoute {
|
||||
public_path: "/v1/messages",
|
||||
route_family: "claude",
|
||||
route_kind: "chat",
|
||||
route_kind: "messages",
|
||||
}),
|
||||
"claude:cli" => Some(InternalFinalizeRoute {
|
||||
public_path: "/v1/messages",
|
||||
route_family: "claude",
|
||||
route_kind: "cli",
|
||||
}),
|
||||
"gemini:chat" => Some(InternalFinalizeRoute {
|
||||
"gemini:generate_content" => Some(InternalFinalizeRoute {
|
||||
public_path: "/v1beta/models",
|
||||
route_family: "gemini",
|
||||
route_kind: "chat",
|
||||
}),
|
||||
"gemini:cli" => Some(InternalFinalizeRoute {
|
||||
public_path: "/v1beta/models",
|
||||
route_family: "gemini",
|
||||
route_kind: "cli",
|
||||
route_kind: "generate_content",
|
||||
}),
|
||||
"gemini:video" => Some(InternalFinalizeRoute {
|
||||
public_path: "/v1beta/models",
|
||||
@@ -525,10 +515,7 @@ mod tests {
|
||||
route_kind: "responses:compact",
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_internal_finalize_route("openai:compact"),
|
||||
resolve_internal_finalize_route("openai:responses:compact")
|
||||
);
|
||||
assert_eq!(resolve_internal_finalize_route("openai:compact"), None);
|
||||
assert_eq!(
|
||||
resolve_internal_finalize_route("gemini:video"),
|
||||
Some(InternalFinalizeRoute {
|
||||
|
||||
@@ -331,7 +331,7 @@ mod tests {
|
||||
"cache_read_input_tokens": 10
|
||||
}
|
||||
}),
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 0);
|
||||
@@ -411,7 +411,7 @@ mod tests {
|
||||
"cache_creation_input_tokens": 4,
|
||||
"cache_read_input_tokens": 1
|
||||
}),
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 10);
|
||||
@@ -447,7 +447,7 @@ mod tests {
|
||||
}
|
||||
]
|
||||
}),
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 5);
|
||||
@@ -474,7 +474,7 @@ mod tests {
|
||||
}
|
||||
]
|
||||
}),
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 5);
|
||||
@@ -492,7 +492,7 @@ mod tests {
|
||||
"cache_read_input_tokens": 52873,
|
||||
"output_tokens": 973
|
||||
}),
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 4941);
|
||||
@@ -520,7 +520,7 @@ mod tests {
|
||||
},
|
||||
"service_tier": "standard"
|
||||
}),
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 2095);
|
||||
@@ -544,7 +544,7 @@ mod tests {
|
||||
"cache_creation_input_tokens": 5191,
|
||||
"cache_read_input_tokens": 97634
|
||||
}),
|
||||
"claude:chat",
|
||||
"claude:messages",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 1);
|
||||
@@ -565,7 +565,7 @@ mod tests {
|
||||
"cachedContentTokenCount": 2
|
||||
}
|
||||
}),
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 14);
|
||||
@@ -596,7 +596,7 @@ mod tests {
|
||||
}
|
||||
]
|
||||
}),
|
||||
"gemini:chat",
|
||||
"gemini:generate_content",
|
||||
);
|
||||
|
||||
assert_eq!(usage.input_tokens, 14);
|
||||
|
||||
@@ -2453,7 +2453,7 @@ mod tests {
|
||||
"messages": [{"role": "user", "content": "hello"}]
|
||||
})),
|
||||
stream: false,
|
||||
client_api_format: "claude:cli".to_string(),
|
||||
client_api_format: "claude:messages".to_string(),
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
@@ -2561,7 +2561,7 @@ mod tests {
|
||||
content_encoding: None,
|
||||
body: RequestBody::from_json(json!({"model": "gpt-5.4"})),
|
||||
stream: true,
|
||||
client_api_format: "claude:cli".to_string(),
|
||||
client_api_format: "claude:messages".to_string(),
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
@@ -2618,7 +2618,7 @@ mod tests {
|
||||
content_encoding: None,
|
||||
body: RequestBody::from_json(json!({"model": "gpt-5.4"})),
|
||||
stream: false,
|
||||
client_api_format: "claude:cli".to_string(),
|
||||
client_api_format: "claude:messages".to_string(),
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
@@ -2629,7 +2629,7 @@ mod tests {
|
||||
trace_id: "trace-sync-usage-deep-1".to_string(),
|
||||
report_kind: "claude_cli_sync_success".to_string(),
|
||||
report_context: Some(json!({
|
||||
"client_api_format": "claude:cli",
|
||||
"client_api_format": "claude:messages",
|
||||
"provider_api_format": "openai:responses",
|
||||
"needs_conversion": true,
|
||||
"original_request_body": nested,
|
||||
@@ -3078,7 +3078,7 @@ mod tests {
|
||||
},
|
||||
stream: false,
|
||||
client_api_format: "openai:chat".to_string(),
|
||||
provider_api_format: "gemini:chat".to_string(),
|
||||
provider_api_format: "gemini:generate_content".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
@@ -3089,7 +3089,7 @@ mod tests {
|
||||
report_kind: "openai_chat_sync_success".to_string(),
|
||||
report_context: Some(json!({
|
||||
"client_api_format": "openai:chat",
|
||||
"provider_api_format": "gemini:chat",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"needs_conversion": true
|
||||
})),
|
||||
status_code: 200,
|
||||
@@ -3293,7 +3293,7 @@ mod tests {
|
||||
},
|
||||
stream: false,
|
||||
client_api_format: "openai:chat".to_string(),
|
||||
provider_api_format: "gemini:chat".to_string(),
|
||||
provider_api_format: "gemini:generate_content".to_string(),
|
||||
model_name: Some("gpt-5".to_string()),
|
||||
proxy: None,
|
||||
tls_profile: None,
|
||||
@@ -3304,7 +3304,7 @@ mod tests {
|
||||
report_kind: "openai_chat_sync_success".to_string(),
|
||||
report_context: Some(json!({
|
||||
"client_api_format": "openai:chat",
|
||||
"provider_api_format": "gemini:chat",
|
||||
"provider_api_format": "gemini:generate_content",
|
||||
"needs_conversion": true
|
||||
})),
|
||||
status_code: 200,
|
||||
@@ -3579,7 +3579,7 @@ mod tests {
|
||||
"input": [{"role": "user", "content": "provider-side compiled body"}],
|
||||
})),
|
||||
stream: false,
|
||||
client_api_format: "claude:cli".to_string(),
|
||||
client_api_format: "claude:messages".to_string(),
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
@@ -3590,7 +3590,7 @@ mod tests {
|
||||
trace_id: "trace-sync-no-client-echo-1".to_string(),
|
||||
report_kind: "claude_cli_sync_success".to_string(),
|
||||
report_context: Some(json!({
|
||||
"client_api_format": "claude:cli",
|
||||
"client_api_format": "claude:messages",
|
||||
"provider_api_format": "openai:responses",
|
||||
"needs_conversion": true,
|
||||
"original_request_body": null,
|
||||
@@ -3633,7 +3633,7 @@ mod tests {
|
||||
content_encoding: None,
|
||||
body: RequestBody::from_json(json!({"model": "gpt-5.4"})),
|
||||
stream: false,
|
||||
client_api_format: "claude:cli".to_string(),
|
||||
client_api_format: "claude:messages".to_string(),
|
||||
provider_api_format: "openai:responses".to_string(),
|
||||
model_name: Some("gpt-5.4".to_string()),
|
||||
proxy: None,
|
||||
@@ -3644,7 +3644,7 @@ mod tests {
|
||||
trace_id: "trace-sync-null-error-1".to_string(),
|
||||
report_kind: "claude_cli_sync_success".to_string(),
|
||||
report_context: Some(json!({
|
||||
"client_api_format": "claude:cli",
|
||||
"client_api_format": "claude:messages",
|
||||
"provider_api_format": "openai:responses",
|
||||
"needs_conversion": true,
|
||||
})),
|
||||
|
||||
Reference in New Issue
Block a user