migrate ai format conversion to responses adapters

This commit is contained in:
fawney19
2026-04-26 20:32:55 +08:00
parent e36fb8c07a
commit 5b914aa78c
183 changed files with 13539 additions and 3401 deletions

View File

@@ -148,12 +148,7 @@ fn local_same_format_transport_unsupported_reason(
Some("key_inactive")
};
}
if !transport
.endpoint
.api_format
.trim()
.eq_ignore_ascii_case(api_format.trim())
{
if !same_api_format(&transport.endpoint.api_format, api_format) {
return Some("transport_api_format_mismatch");
}
if !header_rules_are_locally_supported(transport.endpoint.header_rules.as_ref()) {
@@ -203,3 +198,15 @@ fn local_same_format_transport_unsupported_reason(
None
}
fn same_api_format(left: &str, right: &str) -> bool {
normalize_api_format_alias(left) == normalize_api_format_alias(right)
}
fn normalize_api_format_alias(value: &str) -> String {
match value.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses".to_string(),
"openai:compact" => "openai:responses:compact".to_string(),
other => other.to_string(),
}
}

View File

@@ -75,14 +75,14 @@ const CODEX_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTempla
base_url: "https://chatgpt.com/backend-api/codex",
endpoints: &[
FixedProviderEndpointTemplate {
item_key: "openai:cli",
api_format: "openai:cli",
item_key: "openai:responses",
api_format: "openai:responses",
custom_path: None,
config_defaults: FORCE_STREAM_ENDPOINT_CONFIG_DEFAULTS,
},
FixedProviderEndpointTemplate {
item_key: "openai:compact",
api_format: "openai:compact",
item_key: "openai:responses:compact",
api_format: "openai:responses:compact",
custom_path: None,
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
},
@@ -181,7 +181,11 @@ pub fn fixed_provider_endpoint_template_by_api_format(
provider_type: &str,
api_format: &str,
) -> Option<&'static FixedProviderEndpointTemplate> {
let normalized = api_format.trim();
let normalized = match api_format.trim().to_ascii_lowercase().as_str() {
"openai:cli" => "openai:responses",
"openai:compact" => "openai:responses:compact",
_ => api_format.trim(),
};
fixed_provider_template(provider_type)?
.endpoints
.iter()
@@ -299,7 +303,11 @@ mod tests {
.iter()
.map(|item| item.api_format)
.collect::<Vec<_>>(),
vec!["openai:cli", "openai:compact", "openai:image"]
vec![
"openai:responses",
"openai:responses:compact",
"openai:image"
]
);
let image_template =

View File

@@ -9,7 +9,7 @@ use crate::claude_code::build_claude_code_messages_url;
use crate::snapshot::GatewayProviderTransportSnapshot;
use crate::url::{
build_claude_messages_url, build_gemini_content_url, build_openai_chat_url,
build_openai_cli_url, build_passthrough_path_url,
build_openai_responses_url, build_passthrough_path_url,
};
use crate::vertex::{
build_vertex_api_key_gemini_content_url, resolve_local_vertex_api_key_query_auth,
@@ -65,12 +65,12 @@ pub fn build_transport_request_url(
&transport.endpoint.base_url,
params.request_query,
)),
"openai:cli" => Some(build_openai_cli_url(
"openai:responses" | "openai:cli" => Some(build_openai_responses_url(
&transport.endpoint.base_url,
params.request_query,
false,
)),
"openai:compact" => Some(build_openai_cli_url(
"openai:responses:compact" | "openai:compact" => Some(build_openai_responses_url(
&transport.endpoint.base_url,
params.request_query,
true,
@@ -346,6 +346,30 @@ mod tests {
);
}
#[test]
fn builds_openai_responses_url_for_formal_format_name() {
let transport = sample_transport(
"openai",
"openai:responses",
"https://api.openai.example/v1",
None,
);
let url = build_transport_request_url(
&transport,
TransportRequestUrlParams {
provider_api_format: "openai:responses",
mapped_model: None,
upstream_is_stream: false,
request_query: Some("tenant=demo"),
kiro_api_region: None,
},
)
.expect("openai responses url");
assert_eq!(url, "https://api.openai.example/v1/responses?tenant=demo");
}
#[test]
fn expands_custom_path_templates_when_hook_does_not_apply() {
let transport = sample_transport(

File diff suppressed because it is too large Load Diff

View File

@@ -308,7 +308,7 @@ mod tests {
)
.expect("key should build")
.with_transport_fields(
Some(serde_json::json!(["openai:chat", "openai:cli"])),
Some(serde_json::json!(["openai:chat", "openai:responses"])),
encrypted_api_key,
Some(encrypted_auth_config),
Some(serde_json::json!({"openai:chat": 0.8})),
@@ -382,7 +382,10 @@ mod tests {
name: "prod-key".to_string(),
auth_type: "api_key".to_string(),
is_active: true,
api_formats: Some(vec!["openai:chat".to_string(), "openai:cli".to_string(),]),
api_formats: Some(vec![
"openai:chat".to_string(),
"openai:responses".to_string(),
]),
allowed_models: Some(vec!["gpt-4.1".to_string(), "gpt-4.1-mini".to_string(),]),
capabilities: Some(serde_json::json!({"cache_1h": true})),
rate_multipliers: Some(serde_json::json!({"openai:chat": 0.8})),
@@ -672,9 +675,9 @@ mod tests {
let endpoint = StoredProviderCatalogEndpoint::new(
"endpoint-safe-2".to_string(),
"provider-1".to_string(),
"openai:cli".to_string(),
"openai:responses".to_string(),
Some("openai".to_string()),
Some("cli".to_string()),
Some("responses".to_string()),
true,
)
.expect("endpoint should build")
@@ -707,7 +710,7 @@ mod tests {
)
.expect("key should build")
.with_transport_fields(
Some(serde_json::json!(["openai:cli"])),
Some(serde_json::json!(["openai:responses"])),
encrypted_api_key,
Some(encrypted_auth_config),
None,
@@ -737,7 +740,7 @@ mod tests {
);
assert!(!supports_local_standard_transport_with_network(
&snapshot,
"openai:cli"
"openai:responses"
));
}
@@ -820,9 +823,9 @@ mod tests {
let endpoint = StoredProviderCatalogEndpoint::new(
"endpoint-safe-4".to_string(),
"provider-1".to_string(),
"openai:cli".to_string(),
"openai:responses".to_string(),
Some("openai".to_string()),
Some("cli".to_string()),
Some("responses".to_string()),
true,
)
.expect("endpoint should build")
@@ -862,7 +865,7 @@ mod tests {
)
.expect("key should build")
.with_transport_fields(
Some(serde_json::json!(["openai:cli"])),
Some(serde_json::json!(["openai:responses"])),
encrypted_api_key,
Some(encrypted_auth_config),
None,
@@ -900,7 +903,7 @@ mod tests {
);
assert!(supports_local_standard_transport_with_network(
&snapshot,
"openai:cli"
"openai:responses"
));
}

View File

@@ -15,7 +15,11 @@ pub fn build_openai_chat_url(upstream_base_url: &str, query: Option<&str>) -> St
url
}
pub fn build_openai_cli_url(upstream_base_url: &str, query: Option<&str>, compact: bool) -> String {
pub fn build_openai_responses_url(
upstream_base_url: &str,
query: Option<&str>,
compact: bool,
) -> String {
let (trimmed, base_query) = split_base_url_query(upstream_base_url);
let trimmed = trimmed.trim_end_matches('/');
let suffix = if compact {
@@ -242,8 +246,8 @@ fn merge_query_string(
mod tests {
use super::{
build_gemini_content_url, build_gemini_files_passthrough_url,
build_gemini_video_predict_long_running_url, build_openai_chat_url, build_openai_cli_url,
build_passthrough_path_url,
build_gemini_video_predict_long_running_url, build_openai_chat_url,
build_openai_responses_url, build_passthrough_path_url,
};
#[test]
@@ -258,13 +262,13 @@ mod tests {
}
#[test]
fn openai_cli_url_preserves_codex_path_prefix() {
fn openai_responses_url_preserves_codex_path_prefix() {
assert_eq!(
build_openai_cli_url("https://tiger.bookapi.cc/codex", None, false),
build_openai_responses_url("https://tiger.bookapi.cc/codex", None, false),
"https://tiger.bookapi.cc/codex/responses"
);
assert_eq!(
build_openai_cli_url("https://tiger.bookapi.cc/codex?tenant=demo", None, true),
build_openai_responses_url("https://tiger.bookapi.cc/codex?tenant=demo", None, true),
"https://tiger.bookapi.cc/codex/responses/compact?tenant=demo"
);
}