2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::build_request_trace_proxy_value;
|
2026-05-18 11:03:49 +08:00
|
|
|
use crate::ai_serving::planner::decision_input::apply_provider_request_routing_policy_to_decision;
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::planner::report_context::{
|
2026-04-14 01:27:04 +08:00
|
|
|
build_local_execution_report_context, LocalExecutionReportContextParts,
|
2026-04-09 00:10:38 +08:00
|
|
|
};
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::planner::spec_metadata::local_video_create_spec_metadata;
|
|
|
|
|
use crate::ai_serving::planner::{
|
2026-07-11 03:42:30 +08:00
|
|
|
build_ai_execution_decision_response, resolve_transport_request_encoding_policy,
|
2026-06-10 18:16:55 +08:00
|
|
|
AiExecutionDecisionResponseParts,
|
2026-05-02 13:23:54 +08:00
|
|
|
};
|
2026-09-14 21:17:21 +08:00
|
|
|
use crate::ai_serving::transport::xai::video::is_native_video_request;
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::transport::{
|
2026-05-05 22:21:23 +08:00
|
|
|
resolve_transport_execution_timeouts, resolve_transport_profile,
|
2026-04-09 00:10:38 +08:00
|
|
|
};
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::ai_serving::{ai_local_execution_contract_for_formats, PlannerAppState};
|
2026-07-26 15:06:56 +08:00
|
|
|
use crate::{append_local_failover_policy_to_value, AiExecutionDecision, AppState, GatewayError};
|
2026-04-09 00:10:38 +08:00
|
|
|
|
2026-04-14 01:27:04 +08:00
|
|
|
use super::request::resolve_local_video_create_candidate_payload_parts;
|
|
|
|
|
use super::support::{LocalVideoCreateCandidateAttempt, LocalVideoCreateDecisionInput};
|
|
|
|
|
use super::LocalVideoCreateSpec;
|
2026-04-09 00:10:38 +08:00
|
|
|
|
|
|
|
|
pub(super) async fn maybe_build_local_video_create_decision_payload_for_candidate(
|
|
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
body_json: &serde_json::Value,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
input: &LocalVideoCreateDecisionInput,
|
|
|
|
|
attempt: LocalVideoCreateCandidateAttempt,
|
|
|
|
|
spec: LocalVideoCreateSpec,
|
2026-05-18 11:03:49 +08:00
|
|
|
) -> Result<Option<AiExecutionDecision>, GatewayError> {
|
2026-04-14 01:27:04 +08:00
|
|
|
let spec_metadata = local_video_create_spec_metadata(spec);
|
2026-04-09 00:10:38 +08:00
|
|
|
let planner_state = PlannerAppState::new(state);
|
2026-04-18 11:35:11 +08:00
|
|
|
let attempt_identity = attempt.attempt_identity();
|
2026-05-18 11:03:49 +08:00
|
|
|
let Some(resolved) = resolve_local_video_create_candidate_payload_parts(
|
2026-04-14 01:27:04 +08:00
|
|
|
state, parts, body_json, trace_id, input, &attempt, spec,
|
|
|
|
|
)
|
2026-09-14 21:17:21 +08:00
|
|
|
.await?
|
2026-05-18 11:03:49 +08:00
|
|
|
else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
2026-04-09 00:10:38 +08:00
|
|
|
let LocalVideoCreateCandidateAttempt {
|
2026-04-14 01:27:04 +08:00
|
|
|
eligible,
|
2026-04-09 00:10:38 +08:00
|
|
|
candidate_id,
|
2026-04-18 11:35:11 +08:00
|
|
|
..
|
2026-04-09 00:10:38 +08:00
|
|
|
} = attempt;
|
2026-04-14 01:27:04 +08:00
|
|
|
let candidate = eligible.candidate;
|
|
|
|
|
let transport = resolved.transport;
|
2026-05-02 13:23:54 +08:00
|
|
|
let (execution_strategy, conversion_mode) =
|
|
|
|
|
ai_local_execution_contract_for_formats(spec_metadata.api_format, spec_metadata.api_format);
|
2026-04-14 14:09:24 +08:00
|
|
|
let proxy = planner_state
|
|
|
|
|
.app()
|
|
|
|
|
.resolve_transport_proxy_snapshot_with_tunnel_affinity(&transport)
|
|
|
|
|
.await;
|
2026-05-05 22:21:23 +08:00
|
|
|
let transport_profile = resolve_transport_profile(&transport);
|
2026-04-16 11:57:42 +08:00
|
|
|
let mut extra_fields = serde_json::Map::new();
|
2026-09-14 21:17:21 +08:00
|
|
|
if is_native_video_request(&transport.provider.provider_type, parts.uri.path()) {
|
|
|
|
|
extra_fields.insert(
|
|
|
|
|
"video_client_protocol".to_string(),
|
|
|
|
|
serde_json::json!("xai"),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 11:57:42 +08:00
|
|
|
if let Some(proxy_value) = build_request_trace_proxy_value(Some(&transport), proxy.as_ref()) {
|
|
|
|
|
extra_fields.insert("proxy".to_string(), proxy_value);
|
|
|
|
|
}
|
2026-09-14 21:17:21 +08:00
|
|
|
if transport.provider.provider_type.eq_ignore_ascii_case("xai") {
|
|
|
|
|
extra_fields.insert("video_provider_xai".into(), serde_json::json!(true));
|
|
|
|
|
if let Some(duration) = resolved.provider_request_body.get("duration") {
|
|
|
|
|
extra_fields.insert("video_duration".into(), duration.clone());
|
|
|
|
|
}
|
|
|
|
|
if parts.uri.path() == "/openai/v1/videos" {
|
|
|
|
|
extra_fields.insert(
|
|
|
|
|
"video_size".into(),
|
|
|
|
|
body_json
|
|
|
|
|
.get("size")
|
|
|
|
|
.filter(|v| v.as_str().is_some_and(|s| !s.trim().is_empty()))
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_else(|| serde_json::json!("720x1280")),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-18 11:03:49 +08:00
|
|
|
let effective_headers = input.effective_headers(&parts.headers);
|
2026-04-21 16:19:07 +08:00
|
|
|
let report_context = build_local_execution_report_context(LocalExecutionReportContextParts {
|
|
|
|
|
auth_context: &input.auth_context,
|
|
|
|
|
request_id: trace_id,
|
|
|
|
|
candidate_id: &candidate_id,
|
|
|
|
|
attempt_identity,
|
|
|
|
|
model: &input.requested_model,
|
|
|
|
|
provider_name: &transport.provider.name,
|
|
|
|
|
provider_id: &candidate.provider_id,
|
|
|
|
|
endpoint_id: &candidate.endpoint_id,
|
|
|
|
|
key_id: &candidate.key_id,
|
|
|
|
|
key_name: None,
|
2026-04-24 14:40:28 +08:00
|
|
|
model_id: Some(&candidate.model_id),
|
|
|
|
|
global_model_id: Some(&candidate.global_model_id),
|
|
|
|
|
global_model_name: Some(&candidate.global_model_name),
|
2026-04-21 16:19:07 +08:00
|
|
|
provider_api_format: spec_metadata.api_format,
|
|
|
|
|
client_api_format: spec_metadata.api_format,
|
|
|
|
|
mapped_model: Some(&resolved.mapped_model),
|
|
|
|
|
candidate_group_id: eligible.orchestration.candidate_group_id.as_deref(),
|
2026-05-11 00:12:05 +08:00
|
|
|
pool_key_lease: eligible.orchestration.pool_key_lease.as_ref(),
|
2026-04-27 14:05:19 +08:00
|
|
|
ranking: eligible.ranking.as_ref(),
|
2026-04-21 16:19:07 +08:00
|
|
|
upstream_url: None,
|
2026-04-25 11:43:53 +08:00
|
|
|
header_rules: transport.endpoint.header_rules.as_ref(),
|
|
|
|
|
body_rules: transport.endpoint.body_rules.as_ref(),
|
2026-04-21 16:19:07 +08:00
|
|
|
provider_request_method: None,
|
|
|
|
|
provider_request_headers: None,
|
2026-05-18 11:03:49 +08:00
|
|
|
original_headers: effective_headers,
|
2026-05-09 10:46:04 +08:00
|
|
|
request_path: Some(parts.uri.path()),
|
|
|
|
|
request_query_string: parts.uri.query(),
|
2026-05-02 13:23:54 +08:00
|
|
|
request_origin: Some(crate::ai_serving::request_origin_from_parts(parts)),
|
2026-04-21 16:19:07 +08:00
|
|
|
original_request_body_json: Some(body_json),
|
|
|
|
|
original_request_body_base64: None,
|
2026-05-05 21:34:17 +08:00
|
|
|
client_session_affinity: input.client_session_affinity.as_ref(),
|
2026-07-27 22:06:28 +08:00
|
|
|
routing_policy: input.routing_policy.as_ref(),
|
2026-05-11 01:45:49 +08:00
|
|
|
scheduler_affinity_epoch: eligible.orchestration.scheduler_affinity_epoch,
|
2026-09-02 20:48:40 +08:00
|
|
|
sticky_key_attempts: eligible.orchestration.sticky_key_attempts,
|
2026-04-23 14:42:51 +08:00
|
|
|
client_requested_stream: false,
|
2026-04-23 21:53:50 +08:00
|
|
|
upstream_is_stream: false,
|
2026-04-21 16:19:07 +08:00
|
|
|
has_envelope: false,
|
|
|
|
|
needs_conversion: false,
|
|
|
|
|
extra_fields,
|
|
|
|
|
});
|
2026-07-26 15:06:56 +08:00
|
|
|
let report_context = append_local_failover_policy_to_value(report_context, &transport);
|
2026-04-21 16:19:07 +08:00
|
|
|
let super::request::LocalVideoCreateCandidatePayloadParts {
|
|
|
|
|
transport: _,
|
|
|
|
|
auth_header,
|
|
|
|
|
auth_value,
|
|
|
|
|
mapped_model,
|
|
|
|
|
provider_request_headers,
|
|
|
|
|
provider_request_body,
|
|
|
|
|
upstream_url,
|
|
|
|
|
} = resolved;
|
2026-07-11 03:42:30 +08:00
|
|
|
let request_encoding = resolve_transport_request_encoding_policy(&transport);
|
2026-04-09 00:10:38 +08:00
|
|
|
|
2026-05-18 11:03:49 +08:00
|
|
|
let mut decision = build_ai_execution_decision_response(AiExecutionDecisionResponseParts {
|
|
|
|
|
decision_is_stream: false,
|
|
|
|
|
decision_kind: spec_metadata.decision_kind.to_string(),
|
|
|
|
|
execution_strategy,
|
|
|
|
|
conversion_mode,
|
|
|
|
|
request_id: trace_id.to_string(),
|
|
|
|
|
candidate_id: candidate_id.clone(),
|
|
|
|
|
provider_name: transport.provider.name.clone(),
|
2026-07-11 03:42:30 +08:00
|
|
|
provider_type: transport.provider.provider_type.clone(),
|
2026-05-18 11:03:49 +08:00
|
|
|
provider_id: candidate.provider_id.clone(),
|
|
|
|
|
endpoint_id: candidate.endpoint_id.clone(),
|
|
|
|
|
key_id: candidate.key_id.clone(),
|
|
|
|
|
upstream_base_url: transport.endpoint.base_url.clone(),
|
|
|
|
|
upstream_url,
|
|
|
|
|
provider_request_method: Some(parts.method.to_string()),
|
|
|
|
|
auth_header: Some(auth_header),
|
|
|
|
|
auth_value: Some(auth_value),
|
|
|
|
|
provider_api_format: spec_metadata.api_format.to_string(),
|
|
|
|
|
client_api_format: spec_metadata.api_format.to_string(),
|
|
|
|
|
model_name: input.requested_model.clone(),
|
|
|
|
|
mapped_model,
|
|
|
|
|
prompt_cache_key: None,
|
|
|
|
|
provider_request_headers,
|
|
|
|
|
provider_request_body: Some(provider_request_body),
|
|
|
|
|
provider_request_body_base64: None,
|
|
|
|
|
content_type: parts
|
|
|
|
|
.headers
|
|
|
|
|
.get(http::header::CONTENT_TYPE)
|
|
|
|
|
.and_then(|value| value.to_str().ok())
|
|
|
|
|
.map(str::trim)
|
|
|
|
|
.filter(|value| !value.is_empty())
|
|
|
|
|
.map(ToOwned::to_owned),
|
2026-07-11 03:42:30 +08:00
|
|
|
content_encoding: request_encoding.content_encoding,
|
|
|
|
|
request_gzip: request_encoding.request_gzip,
|
2026-05-18 11:03:49 +08:00
|
|
|
proxy,
|
|
|
|
|
transport_profile,
|
|
|
|
|
timeouts: resolve_transport_execution_timeouts(&transport),
|
|
|
|
|
upstream_is_stream: false,
|
|
|
|
|
report_kind: spec_metadata.report_kind.map(ToOwned::to_owned),
|
|
|
|
|
report_context: Some(report_context),
|
|
|
|
|
auth_context: input.auth_context.clone(),
|
|
|
|
|
});
|
2026-07-11 03:42:30 +08:00
|
|
|
apply_provider_request_routing_policy_to_decision(
|
|
|
|
|
input,
|
|
|
|
|
&mut decision,
|
|
|
|
|
Some(transport.as_ref()),
|
|
|
|
|
)?;
|
2026-05-18 11:03:49 +08:00
|
|
|
Ok(Some(decision))
|
2026-04-09 00:10:38 +08:00
|
|
|
}
|