feat(codex): align Search and execution protocol

This commit is contained in:
MMEXA
2026-07-12 03:04:15 +08:00
parent 59d37ae1dd
commit 2316df5c9a
121 changed files with 5141 additions and 543 deletions
@@ -354,9 +354,9 @@ fn transport_key_supports_api_format(
match transport.key.api_formats.as_deref() {
None => true,
Some(formats) => formats
.iter()
.any(|value| aether_ai_formats::api_format_alias_matches(value, endpoint_api_format)),
Some(formats) => formats.iter().any(|value| {
aether_ai_formats::api_format_permission_covers(value, endpoint_api_format)
}),
}
}
@@ -818,6 +818,29 @@ mod tests {
);
}
#[test]
fn responses_key_permission_covers_search_without_changing_endpoint_identity() {
let mut transport = transport_snapshot("custom", "openai:search", "bearer", true, None);
transport.key.api_formats = Some(vec!["openai:responses".to_string()]);
assert_eq!(
candidate_common_transport_skip_reason(
&transport,
candidate_facts("openai:search"),
None,
),
None
);
assert_eq!(
candidate_common_transport_skip_reason(
&transport,
candidate_facts("openai:responses"),
None,
),
Some("endpoint_api_format_changed")
);
}
#[test]
fn candidate_transport_pair_policy_reports_disabled_conversion_and_unsupported_pairs() {
let transport = transport_snapshot("custom", "openai:responses", "bearer", false, None);
@@ -454,6 +454,21 @@ mod tests {
assert_eq!(timeouts.first_byte_ms, Some(30_000));
}
#[test]
fn transport_execution_timeouts_preserve_the_configurable_maximum() {
let mut transport = sample_transport();
transport.provider.request_timeout_secs =
Some(aether_contracts::MAX_EXECUTION_REQUEST_TIMEOUT_SECS as f64);
let timeouts = resolve_transport_execution_timeouts(&transport)
.expect("provider timeouts should resolve");
assert_eq!(
timeouts.total_ms,
Some(aether_contracts::MAX_EXECUTION_REQUEST_TIMEOUT_MS)
);
}
#[test]
fn transport_execution_timeouts_preserve_configured_first_byte_value() {
let mut transport = sample_transport();
@@ -306,6 +306,12 @@ const CODEX_FIXED_PROVIDER_TEMPLATE: FixedProviderTemplate = FixedProviderTempla
custom_path: None,
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
},
FixedProviderEndpointTemplate {
item_key: "openai:search",
api_format: "openai:search",
custom_path: None,
config_defaults: EMPTY_ENDPOINT_CONFIG_DEFAULTS,
},
FixedProviderEndpointTemplate {
item_key: "openai:image",
api_format: "openai:image",
@@ -639,7 +645,7 @@ mod tests {
};
#[test]
fn codex_fixed_provider_template_includes_openai_image() {
fn codex_fixed_provider_template_includes_codex_companion_endpoints() {
let template = fixed_provider_template("codex").expect("codex template should exist");
assert_eq!(template.base_url, "https://chatgpt.com/backend-api/codex");
assert_eq!(template.version, 1);
@@ -652,6 +658,7 @@ mod tests {
vec![
"openai:responses",
"openai:responses:compact",
"openai:search",
"openai:image"
]
);
@@ -660,6 +667,11 @@ mod tests {
fixed_provider_endpoint_template_by_api_format("codex", "openai:image")
.expect("codex image endpoint should exist");
assert!(image_template.config_defaults.is_empty());
let search_template =
fixed_provider_endpoint_template_by_api_format("codex", "openai:search")
.expect("codex search endpoint should exist");
assert!(search_template.config_defaults.is_empty());
}
#[test]
@@ -16,7 +16,8 @@ use crate::gemini_cli::{
use crate::snapshot::GatewayProviderTransportSnapshot;
use crate::url::{
build_claude_messages_url, build_gemini_content_url, build_openai_chat_url,
build_openai_responses_url, build_passthrough_path_url, normalize_gemini_content_action_path,
build_openai_responses_url, build_openai_search_url, build_passthrough_path_url,
normalize_gemini_content_action_path,
};
use crate::vertex::{
build_vertex_api_key_gemini_content_url, build_vertex_api_key_gemini_embedding_url,
@@ -124,6 +125,10 @@ fn build_transport_request_url_inner(
params.request_query,
true,
)),
"openai:search" => Some(build_openai_search_url(
&transport.endpoint.base_url,
params.request_query,
)),
"openai:embedding" | "jina:embedding" => {
build_provider_embedding_v1_url(&transport.endpoint.base_url, params.request_query)
}
@@ -868,6 +873,33 @@ mod tests {
assert_eq!(url, "https://api.openai.example/v1/responses?tenant=demo");
}
#[test]
fn builds_openai_search_url_for_codex_provider_root() {
let transport = sample_transport(
"codex",
"openai:search",
"https://chatgpt.com/backend-api/codex",
None,
);
let url = build_transport_request_url(
&transport,
TransportRequestUrlParams {
provider_api_format: "openai:search",
mapped_model: Some("gpt-5.6-luna"),
upstream_is_stream: false,
request_query: Some("tenant=demo"),
kiro_api_region: None,
},
)
.expect("openai search url");
assert_eq!(
url,
"https://chatgpt.com/backend-api/codex/alpha/search?tenant=demo"
);
}
#[test]
fn expands_custom_path_templates_when_hook_does_not_apply() {
let transport = sample_transport(
@@ -149,9 +149,11 @@ pub fn classify_same_format_provider_request_behavior(
params.require_streaming,
is_kiro || is_antigravity || gemini_cli_requires_upstream_streaming,
);
let force_body_stream_field = aether_ai_formats::endpoint_config_forces_upstream_stream_policy(
transport.endpoint.config.as_ref(),
);
let force_body_stream_field =
aether_ai_formats::api_format_uses_body_stream_field(params.provider_api_format)
&& aether_ai_formats::endpoint_config_forces_upstream_stream_policy(
transport.endpoint.config.as_ref(),
);
let report_kind = if is_kiro && !params.require_streaming {
"claude_cli_sync_finalize"
} else if (is_gemini_cli || is_antigravity) && !params.require_streaming {
@@ -622,6 +624,7 @@ pub fn same_format_provider_transport_unsupported_reason_for_trace(
"openai:chat" => "openai:chat",
"openai:responses" => "openai:responses",
"openai:responses:compact" => "openai:responses:compact",
"openai:search" => "openai:search",
"claude:messages" => "claude:messages",
"gemini:generate_content" => "gemini:generate_content",
"gemini:interactions" => "gemini:interactions",
@@ -702,7 +705,9 @@ fn resolve_same_format_standard_direct_auth(
transport: &GatewayProviderTransportSnapshot,
provider_api_format: &str,
) -> Option<(String, String)> {
if aether_ai_formats::api_format_alias_matches(provider_api_format, "openai:embedding") {
if aether_ai_formats::api_format_alias_matches(provider_api_format, "openai:embedding")
|| aether_ai_formats::api_format_alias_matches(provider_api_format, "openai:search")
{
resolve_local_openai_bearer_auth(transport)
} else {
resolve_local_standard_auth(transport)
@@ -886,6 +891,21 @@ mod tests {
},
);
assert!(!compact_behavior.upstream_is_stream);
let mut search = sample_transport("codex");
search.endpoint.config = Some(json!({
"upstream_stream_policy": "force_stream"
}));
let search_behavior = classify_same_format_provider_request_behavior(
&search,
SameFormatProviderRequestBehaviorParams {
require_streaming: true,
provider_api_format: "openai:search",
report_kind: "openai_search_sync_success",
},
);
assert!(!search_behavior.upstream_is_stream);
assert!(!search_behavior.force_body_stream_field);
}
#[test]
@@ -1035,6 +1055,31 @@ mod tests {
);
}
#[test]
fn resolves_openai_search_direct_auth_with_bearer_header() {
let mut transport = sample_transport("custom");
transport.endpoint.api_format = "openai:search".to_string();
transport.key.auth_type = "api_key".to_string();
let behavior = classify_same_format_provider_request_behavior(
&transport,
SameFormatProviderRequestBehaviorParams {
require_streaming: false,
provider_api_format: "openai:search",
report_kind: "openai_search_sync_success",
},
);
assert_eq!(
resolve_same_format_provider_direct_auth(
&behavior,
&transport,
SameFormatProviderFamily::Standard,
"openai:search",
),
Some(("authorization".to_string(), "Bearer secret".to_string()))
);
}
#[test]
fn keeps_claude_same_format_api_key_on_x_api_key_header() {
let mut transport = sample_transport("custom");
@@ -1770,6 +1815,44 @@ mod tests {
assert_eq!(body["metadata"]["body_rule_seen"], true);
}
#[test]
fn search_body_projects_the_protocol_contract_and_keeps_fast_routing_only() {
let body = build_same_format_provider_request_body(SameFormatProviderRequestBodyInput {
body_json: &json!({
"id": "session-1",
"model": "gpt-5.6-luna-high-fast",
"commands": {"search_query": [{"q": "Aether"}]},
"settings": {"allowed_callers": ["direct"]},
"store": false,
"future_extension": {"enabled": true},
"stream": true
}),
mapped_model: "gpt-5.6-luna",
client_api_format: "openai:search",
provider_api_format: "openai:search",
source_model: Some("gpt-5.6-luna-high-fast"),
family: SameFormatProviderFamily::Standard,
body_rules: None,
request_headers: None,
upstream_is_stream: false,
force_body_stream_field: false,
kiro_auth_config: None,
is_claude_code: false,
enable_model_directives: true,
})
.expect("search body should build");
assert_eq!(body["id"], "session-1");
assert_eq!(body["model"], "gpt-5.6-luna");
assert_eq!(body["commands"]["search_query"][0]["q"], "Aether");
assert_eq!(body["settings"]["allowed_callers"][0], "direct");
assert_eq!(body["reasoning"]["effort"], "high");
assert!(body.get("store").is_none());
assert!(body.get("future_extension").is_none());
assert!(body.get("service_tier").is_none());
assert!(body.get("stream").is_none());
}
#[test]
fn builds_same_format_headers_with_auth_and_stream_accept() {
let provider_request_body = json!({"model": "upstream-model"});
+24 -1
View File
@@ -28,6 +28,14 @@ pub fn build_openai_responses_url(
url
}
pub fn build_openai_search_url(upstream_base_url: &str, query: Option<&str>) -> String {
let (trimmed, base_query) = split_base_url_query(upstream_base_url);
let trimmed = trimmed.trim_end_matches('/');
let mut url = format!("{trimmed}/alpha/search");
append_merged_query(&mut url, base_query, None, query, &[]);
url
}
pub fn build_openai_image_url(
upstream_base_url: &str,
request_path: Option<&str>,
@@ -424,7 +432,7 @@ mod tests {
build_bigmodel_coding_models_url, build_claude_messages_url, build_gemini_content_url,
build_gemini_files_passthrough_url, build_gemini_video_predict_long_running_url,
build_openai_chat_url, build_openai_compatible_models_url, build_openai_image_url,
build_openai_responses_url, build_passthrough_path_url,
build_openai_responses_url, build_openai_search_url, build_passthrough_path_url,
normalize_gemini_content_action_path,
};
@@ -573,6 +581,21 @@ mod tests {
);
}
#[test]
fn openai_search_url_preserves_api_and_codex_roots() {
assert_eq!(
build_openai_search_url(
"https://api.openai.com/v1?tenant=base",
Some("trace=1&tenant=request")
),
"https://api.openai.com/v1/alpha/search?tenant=request&trace=1"
);
assert_eq!(
build_openai_search_url("https://chatgpt.com/backend-api/codex/", None),
"https://chatgpt.com/backend-api/codex/alpha/search"
);
}
#[test]
fn openai_image_url_uses_images_surface() {
assert_eq!(