2026-05-02 13:23:54 +08:00
|
|
|
use aether_ai_serving::{
|
|
|
|
|
build_ai_execution_decision_from_plan, infer_ai_upstream_base_url, run_ai_sync_decision_path,
|
|
|
|
|
AiExecutionDecisionFromPlanParts, AiSyncDecisionPathPort, AiSyncDecisionStep,
|
|
|
|
|
};
|
|
|
|
|
use async_trait::async_trait;
|
|
|
|
|
use tracing::debug;
|
|
|
|
|
|
|
|
|
|
use crate::ai_serving::planner::common::{
|
|
|
|
|
EXECUTION_RUNTIME_SYNC_DECISION_ACTION, GEMINI_FILES_DELETE_PLAN_KIND,
|
|
|
|
|
GEMINI_FILES_GET_PLAN_KIND, GEMINI_FILES_LIST_PLAN_KIND, GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND,
|
|
|
|
|
OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND, OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND,
|
|
|
|
|
OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
|
|
|
|
|
};
|
|
|
|
|
use crate::ai_serving::planner::route::resolve_execution_runtime_sync_plan_kind;
|
|
|
|
|
use crate::ai_serving::{
|
|
|
|
|
build_execution_runtime_auth_context, resolve_execution_runtime_auth_context,
|
|
|
|
|
GatewayControlDecision,
|
|
|
|
|
};
|
2026-09-04 03:45:52 +08:00
|
|
|
use crate::state::VideoTaskRouteAccess;
|
2026-05-02 13:23:54 +08:00
|
|
|
use crate::{AiExecutionDecision, AppState, GatewayError};
|
|
|
|
|
|
|
|
|
|
pub(crate) async fn maybe_build_sync_decision_payload(
|
|
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
body_json: &serde_json::Value,
|
|
|
|
|
body_base64: Option<&str>,
|
|
|
|
|
body_is_empty: bool,
|
|
|
|
|
) -> Result<Option<AiExecutionDecision>, GatewayError> {
|
|
|
|
|
let Some(plan_kind) = resolve_execution_runtime_sync_plan_kind(parts, decision) else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let port = GatewaySyncDecisionPathPort {
|
|
|
|
|
state,
|
|
|
|
|
parts,
|
|
|
|
|
trace_id,
|
|
|
|
|
decision,
|
|
|
|
|
body_json,
|
|
|
|
|
body_base64,
|
|
|
|
|
body_is_empty,
|
|
|
|
|
plan_kind,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
run_ai_sync_decision_path(&port).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct GatewaySyncDecisionPathPort<'a> {
|
|
|
|
|
state: &'a AppState,
|
|
|
|
|
parts: &'a http::request::Parts,
|
|
|
|
|
trace_id: &'a str,
|
|
|
|
|
decision: &'a GatewayControlDecision,
|
|
|
|
|
body_json: &'a serde_json::Value,
|
|
|
|
|
body_base64: Option<&'a str>,
|
|
|
|
|
body_is_empty: bool,
|
|
|
|
|
plan_kind: &'a str,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl AiSyncDecisionPathPort for GatewaySyncDecisionPathPort<'_> {
|
|
|
|
|
type Decision = AiExecutionDecision;
|
|
|
|
|
type Error = GatewayError;
|
|
|
|
|
|
|
|
|
|
fn sync_decision_step_enabled(&self, step: AiSyncDecisionStep) -> bool {
|
|
|
|
|
if step == AiSyncDecisionStep::LocalGeminiFiles {
|
|
|
|
|
return matches!(
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
GEMINI_FILES_LIST_PLAN_KIND
|
|
|
|
|
| GEMINI_FILES_GET_PLAN_KIND
|
|
|
|
|
| GEMINI_FILES_DELETE_PLAN_KIND
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn build_sync_decision_step(
|
|
|
|
|
&self,
|
|
|
|
|
step: AiSyncDecisionStep,
|
|
|
|
|
) -> Result<Option<Self::Decision>, Self::Error> {
|
|
|
|
|
match step {
|
|
|
|
|
AiSyncDecisionStep::VideoTaskFollowUp => {
|
|
|
|
|
maybe_build_local_video_task_follow_up_sync_decision_payload(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.body_json,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
self.decision,
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
AiSyncDecisionStep::LocalVideo => {
|
|
|
|
|
super::maybe_build_sync_local_video_decision_payload(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.body_json,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
self.decision,
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
AiSyncDecisionStep::LocalImage => {
|
|
|
|
|
super::maybe_build_sync_local_image_decision_payload(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.body_json,
|
|
|
|
|
self.body_base64,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
self.decision,
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
AiSyncDecisionStep::LocalOpenAiChat => {
|
|
|
|
|
super::maybe_build_sync_local_decision_payload(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
self.decision,
|
|
|
|
|
self.body_json,
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
AiSyncDecisionStep::LocalOpenAiResponses => {
|
|
|
|
|
super::maybe_build_sync_local_openai_responses_decision_payload(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
self.decision,
|
|
|
|
|
self.body_json,
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
AiSyncDecisionStep::LocalStandardFamily => {
|
|
|
|
|
super::maybe_build_sync_local_standard_decision_payload(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
self.decision,
|
|
|
|
|
self.body_json,
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
AiSyncDecisionStep::LocalSameFormatProvider => {
|
|
|
|
|
super::maybe_build_sync_local_same_format_provider_decision_payload(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
self.decision,
|
|
|
|
|
self.body_json,
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
AiSyncDecisionStep::LocalGeminiFiles => {
|
|
|
|
|
super::maybe_build_sync_local_gemini_files_decision_payload(
|
|
|
|
|
self.state,
|
|
|
|
|
self.parts,
|
|
|
|
|
self.body_json,
|
|
|
|
|
self.body_base64,
|
|
|
|
|
self.body_is_empty,
|
|
|
|
|
self.trace_id,
|
|
|
|
|
self.decision,
|
|
|
|
|
self.plan_kind,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn maybe_build_local_video_task_follow_up_sync_decision_payload(
|
|
|
|
|
state: &AppState,
|
|
|
|
|
parts: &http::request::Parts,
|
|
|
|
|
body_json: &serde_json::Value,
|
|
|
|
|
trace_id: &str,
|
|
|
|
|
decision: &GatewayControlDecision,
|
|
|
|
|
plan_kind: &str,
|
|
|
|
|
) -> Result<Option<AiExecutionDecision>, GatewayError> {
|
|
|
|
|
if !matches!(
|
|
|
|
|
plan_kind,
|
|
|
|
|
OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND
|
|
|
|
|
| OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND
|
|
|
|
|
| OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND
|
|
|
|
|
| GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND
|
|
|
|
|
) {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let auth_context = resolve_execution_runtime_auth_context(
|
|
|
|
|
state,
|
|
|
|
|
decision,
|
|
|
|
|
&parts.headers,
|
|
|
|
|
&parts.uri,
|
|
|
|
|
trace_id,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
let Some(auth_context) = auth_context else {
|
2026-09-04 03:45:52 +08:00
|
|
|
return Err(crate::video_tasks::not_found_error());
|
2026-05-02 13:23:54 +08:00
|
|
|
};
|
2026-09-04 03:45:52 +08:00
|
|
|
if !auth_context.access_allowed || auth_context.user_id.trim().is_empty() {
|
|
|
|
|
return Err(crate::video_tasks::not_found_error());
|
|
|
|
|
}
|
|
|
|
|
if state
|
|
|
|
|
.hydrate_video_task_for_route_for_user(
|
|
|
|
|
decision.route_family.as_deref(),
|
|
|
|
|
parts.uri.path(),
|
|
|
|
|
&auth_context.user_id,
|
|
|
|
|
)
|
|
|
|
|
.await?
|
|
|
|
|
!= VideoTaskRouteAccess::Allowed
|
|
|
|
|
{
|
|
|
|
|
return Err(crate::video_tasks::not_found_error());
|
|
|
|
|
}
|
|
|
|
|
let Some(follow_up) = state.video_tasks.prepare_follow_up_sync_plan_for_user(
|
2026-05-02 13:23:54 +08:00
|
|
|
plan_kind,
|
|
|
|
|
parts.uri.path(),
|
|
|
|
|
Some(body_json),
|
|
|
|
|
Some(&auth_context),
|
|
|
|
|
trace_id,
|
|
|
|
|
) else {
|
2026-09-04 03:45:52 +08:00
|
|
|
return Err(crate::video_tasks::not_found_error());
|
2026-05-02 13:23:54 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let aether_video_tasks_core::LocalVideoTaskFollowUpPlan {
|
|
|
|
|
plan,
|
|
|
|
|
report_kind,
|
|
|
|
|
report_context,
|
|
|
|
|
} = follow_up;
|
|
|
|
|
let upstream_base_url = infer_ai_upstream_base_url(&plan.url);
|
|
|
|
|
|
|
|
|
|
debug!(
|
|
|
|
|
event_name = "local_video_follow_up_sync_decision_payload_built",
|
|
|
|
|
log_type = "debug",
|
|
|
|
|
trace_id = %trace_id,
|
|
|
|
|
request_id = %trace_id,
|
|
|
|
|
candidate_id = ?plan.candidate_id,
|
|
|
|
|
provider_id = %plan.provider_id,
|
|
|
|
|
endpoint_id = %plan.endpoint_id,
|
|
|
|
|
key_id = %plan.key_id,
|
|
|
|
|
plan_kind,
|
|
|
|
|
downstream_path = %parts.uri.path(),
|
|
|
|
|
provider_api_format = %plan.provider_api_format,
|
|
|
|
|
client_api_format = %plan.client_api_format,
|
2026-09-04 03:45:52 +08:00
|
|
|
upstream_origin = %crate::handlers::shared::security_log_url_origin(&plan.url),
|
2026-05-02 13:23:54 +08:00
|
|
|
"gateway built local video follow-up sync decision payload"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(Some(build_ai_execution_decision_from_plan(
|
|
|
|
|
AiExecutionDecisionFromPlanParts {
|
|
|
|
|
action: EXECUTION_RUNTIME_SYNC_DECISION_ACTION.to_string(),
|
|
|
|
|
decision_kind: Some(plan_kind.to_string()),
|
|
|
|
|
request_id: Some(trace_id.to_string()),
|
|
|
|
|
upstream_base_url,
|
|
|
|
|
include_auth_pair: true,
|
|
|
|
|
plan,
|
|
|
|
|
report_kind,
|
|
|
|
|
report_context,
|
|
|
|
|
auth_context: Some(build_execution_runtime_auth_context(&auth_context)),
|
|
|
|
|
},
|
|
|
|
|
)))
|
|
|
|
|
}
|