Normalize canonical API formats

This commit is contained in:
fawney19
2026-04-29 09:25:19 +08:00
parent 02ad67fe33
commit 07a319259b
171 changed files with 2460 additions and 1962 deletions

View File

@@ -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",
});

View File

@@ -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"
));
}
}

View File

@@ -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,
};

View File

@@ -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,
}
}

View File

@@ -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([
(

View File

@@ -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,

View File

@@ -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

View File

@@ -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,
});

View File

@@ -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,
)

View File

@@ -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"
));
}
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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");