refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构

- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
This commit is contained in:
fawney19
2026-04-03 16:26:16 +08:00
parent 8f26e1a31f
commit 1d9c77522a
868 changed files with 1734 additions and 2432 deletions
@@ -1,40 +1,22 @@
use axum::body::Bytes;
use base64::Engine as _;
pub(crate) use crate::gateway::ai_pipeline::contracts::{
CLAUDE_CHAT_STREAM_PLAN_KIND, CLAUDE_CHAT_SYNC_PLAN_KIND, CLAUDE_CLI_STREAM_PLAN_KIND,
CLAUDE_CLI_SYNC_PLAN_KIND, EXECUTION_RUNTIME_STREAM_ACTION,
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_ACTION,
EXECUTION_RUNTIME_SYNC_DECISION_ACTION, GEMINI_CHAT_STREAM_PLAN_KIND,
GEMINI_CHAT_SYNC_PLAN_KIND, GEMINI_CLI_STREAM_PLAN_KIND, GEMINI_CLI_SYNC_PLAN_KIND,
GEMINI_FILES_DELETE_PLAN_KIND, GEMINI_FILES_DOWNLOAD_PLAN_KIND, GEMINI_FILES_GET_PLAN_KIND,
GEMINI_FILES_LIST_PLAN_KIND, GEMINI_FILES_UPLOAD_PLAN_KIND, GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND,
GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND, OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND,
OPENAI_CLI_STREAM_PLAN_KIND, OPENAI_CLI_SYNC_PLAN_KIND, OPENAI_COMPACT_STREAM_PLAN_KIND,
OPENAI_COMPACT_SYNC_PLAN_KIND, OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND,
OPENAI_VIDEO_CONTENT_PLAN_KIND, OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND, OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
};
use crate::gateway::headers::is_json_request;
pub(crate) const GEMINI_FILES_GET_PLAN_KIND: &str = "gemini_files_get";
pub(crate) const GEMINI_FILES_UPLOAD_PLAN_KIND: &str = "gemini_files_upload";
pub(crate) const GEMINI_FILES_LIST_PLAN_KIND: &str = "gemini_files_list";
pub(crate) const GEMINI_FILES_DELETE_PLAN_KIND: &str = "gemini_files_delete";
pub(crate) const GEMINI_FILES_DOWNLOAD_PLAN_KIND: &str = "gemini_files_download";
pub(crate) const OPENAI_VIDEO_CONTENT_PLAN_KIND: &str = "openai_video_content";
pub(crate) const OPENAI_VIDEO_CANCEL_SYNC_PLAN_KIND: &str = "openai_video_cancel_sync";
pub(crate) const OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND: &str = "openai_video_remix_sync";
pub(crate) const OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND: &str = "openai_video_delete_sync";
pub(crate) const GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND: &str = "gemini_video_create_sync";
pub(crate) const GEMINI_VIDEO_CANCEL_SYNC_PLAN_KIND: &str = "gemini_video_cancel_sync";
pub(crate) const OPENAI_CHAT_STREAM_PLAN_KIND: &str = "openai_chat_stream";
pub(crate) const CLAUDE_CHAT_STREAM_PLAN_KIND: &str = "claude_chat_stream";
pub(crate) const GEMINI_CHAT_STREAM_PLAN_KIND: &str = "gemini_chat_stream";
pub(crate) const OPENAI_CLI_STREAM_PLAN_KIND: &str = "openai_cli_stream";
pub(crate) const OPENAI_COMPACT_STREAM_PLAN_KIND: &str = "openai_compact_stream";
pub(crate) const CLAUDE_CLI_STREAM_PLAN_KIND: &str = "claude_cli_stream";
pub(crate) const GEMINI_CLI_STREAM_PLAN_KIND: &str = "gemini_cli_stream";
pub(crate) const OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND: &str = "openai_video_create_sync";
pub(crate) const OPENAI_CHAT_SYNC_PLAN_KIND: &str = "openai_chat_sync";
pub(crate) const OPENAI_CLI_SYNC_PLAN_KIND: &str = "openai_cli_sync";
pub(crate) const OPENAI_COMPACT_SYNC_PLAN_KIND: &str = "openai_compact_sync";
pub(crate) const CLAUDE_CHAT_SYNC_PLAN_KIND: &str = "claude_chat_sync";
pub(crate) const GEMINI_CHAT_SYNC_PLAN_KIND: &str = "gemini_chat_sync";
pub(crate) const CLAUDE_CLI_SYNC_PLAN_KIND: &str = "claude_cli_sync";
pub(crate) const GEMINI_CLI_SYNC_PLAN_KIND: &str = "gemini_cli_sync";
pub(crate) const EXECUTION_RUNTIME_SYNC_ACTION: &str = "execution_runtime_sync";
pub(crate) const EXECUTION_RUNTIME_SYNC_DECISION_ACTION: &str = "execution_runtime_sync_decision";
pub(crate) const EXECUTION_RUNTIME_STREAM_ACTION: &str = "execution_runtime_stream";
pub(crate) const EXECUTION_RUNTIME_STREAM_DECISION_ACTION: &str =
"execution_runtime_stream_decision";
pub(crate) fn parse_direct_request_body(
parts: &http::request::Parts,
body_bytes: &Bytes,
@@ -1,162 +0,0 @@
use std::collections::BTreeMap;
use aether_contracts::{ExecutionPlan, ExecutionTimeouts, ProxySnapshot};
use serde::{Deserialize, Serialize};
use tracing::warn;
use crate::gateway::headers::collect_control_headers;
use crate::gateway::{AppState, GatewayControlAuthContext, GatewayControlDecision, GatewayError};
#[derive(Debug, Serialize)]
pub(crate) struct GatewayControlPlanRequest {
pub(crate) trace_id: String,
pub(crate) method: String,
pub(crate) path: String,
pub(crate) query_string: Option<String>,
pub(crate) headers: BTreeMap<String, String>,
pub(crate) body_json: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) body_base64: Option<String>,
pub(crate) auth_context: Option<GatewayControlAuthContext>,
}
#[derive(Debug, Deserialize, Serialize)]
pub(crate) struct GatewayControlPlanResponse {
pub(crate) action: String,
#[serde(default)]
pub(crate) plan_kind: Option<String>,
#[serde(default)]
pub(crate) plan: Option<ExecutionPlan>,
#[serde(default)]
pub(crate) report_kind: Option<String>,
#[serde(default)]
pub(crate) report_context: Option<serde_json::Value>,
#[serde(default)]
pub(crate) auth_context: Option<GatewayControlAuthContext>,
}
#[derive(Debug, Deserialize, Serialize)]
pub(crate) struct GatewayControlSyncDecisionResponse {
pub(crate) action: String,
#[serde(default)]
pub(crate) decision_kind: Option<String>,
#[serde(default)]
pub(crate) execution_strategy: Option<String>,
#[serde(default)]
pub(crate) conversion_mode: Option<String>,
#[serde(default)]
pub(crate) request_id: Option<String>,
#[serde(default)]
pub(crate) candidate_id: Option<String>,
#[serde(default)]
pub(crate) provider_name: Option<String>,
#[serde(default)]
pub(crate) provider_id: Option<String>,
#[serde(default)]
pub(crate) endpoint_id: Option<String>,
#[serde(default)]
pub(crate) key_id: Option<String>,
#[serde(default)]
pub(crate) upstream_base_url: Option<String>,
#[serde(default)]
pub(crate) upstream_url: Option<String>,
#[serde(default)]
pub(crate) provider_request_method: Option<String>,
#[serde(default)]
pub(crate) auth_header: Option<String>,
#[serde(default)]
pub(crate) auth_value: Option<String>,
#[serde(default)]
pub(crate) provider_api_format: Option<String>,
#[serde(default)]
pub(crate) client_api_format: Option<String>,
#[serde(default)]
pub(crate) provider_contract: Option<String>,
#[serde(default)]
pub(crate) client_contract: Option<String>,
#[serde(default)]
pub(crate) model_name: Option<String>,
#[serde(default)]
pub(crate) mapped_model: Option<String>,
#[serde(default)]
pub(crate) prompt_cache_key: Option<String>,
#[serde(default)]
pub(crate) extra_headers: BTreeMap<String, String>,
#[serde(default)]
pub(crate) provider_request_headers: BTreeMap<String, String>,
#[serde(default)]
pub(crate) provider_request_body: Option<serde_json::Value>,
#[serde(default)]
pub(crate) provider_request_body_base64: Option<String>,
#[serde(default)]
pub(crate) content_type: Option<String>,
#[serde(default)]
pub(crate) proxy: Option<ProxySnapshot>,
#[serde(default)]
pub(crate) tls_profile: Option<String>,
#[serde(default)]
pub(crate) timeouts: Option<ExecutionTimeouts>,
#[serde(default)]
pub(crate) upstream_is_stream: bool,
#[serde(default)]
pub(crate) report_kind: Option<String>,
#[serde(default)]
pub(crate) report_context: Option<serde_json::Value>,
#[serde(default)]
pub(crate) auth_context: Option<GatewayControlAuthContext>,
}
fn decision_has_exact_provider_request(payload: &GatewayControlSyncDecisionResponse) -> bool {
!payload.provider_request_headers.is_empty()
&& (payload.provider_request_body.is_some()
|| payload
.provider_request_body_base64
.as_ref()
.map(|value| !value.trim().is_empty())
.unwrap_or(false))
}
pub(crate) fn generic_decision_missing_exact_provider_request(
payload: &GatewayControlSyncDecisionResponse,
) -> bool {
if decision_has_exact_provider_request(payload) {
return false;
}
warn!(
decision_kind = payload.decision_kind.as_deref().unwrap_or_default(),
provider_api_format = payload.provider_api_format.as_deref().unwrap_or_default(),
client_api_format = payload.client_api_format.as_deref().unwrap_or_default(),
"gateway generic decision missing exact provider request; falling back to plan"
);
true
}
pub(crate) async fn build_gateway_plan_request(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: serde_json::Value,
body_base64: Option<String>,
) -> Result<GatewayControlPlanRequest, GatewayError> {
let auth_context = crate::gateway::resolve_execution_runtime_auth_context(
state,
decision,
&parts.headers,
&parts.uri,
trace_id,
)
.await?;
Ok(GatewayControlPlanRequest {
trace_id: trace_id.to_string(),
method: parts.method.to_string(),
path: parts.uri.path().to_string(),
query_string: parts.uri.query().map(ToOwned::to_owned),
headers: collect_control_headers(&parts.headers),
body_json,
body_base64,
auth_context,
})
}
@@ -1,132 +0,0 @@
use axum::body::Body;
use axum::http::Response;
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::request_candidates::record_local_request_candidate_status;
use crate::gateway::{
execute_execution_runtime_stream, execute_execution_runtime_sync, AppState,
GatewayControlDecision, GatewayError,
};
pub(crate) trait LocalPlanAndReport {
fn plan(&self) -> &aether_contracts::ExecutionPlan;
fn report_kind(&self) -> Option<String>;
fn report_context(&self) -> Option<serde_json::Value>;
}
impl LocalPlanAndReport for LocalSyncPlanAndReport {
fn plan(&self) -> &aether_contracts::ExecutionPlan {
&self.plan
}
fn report_kind(&self) -> Option<String> {
self.report_kind.clone()
}
fn report_context(&self) -> Option<serde_json::Value> {
self.report_context.clone()
}
}
impl LocalPlanAndReport for LocalStreamPlanAndReport {
fn plan(&self) -> &aether_contracts::ExecutionPlan {
&self.plan
}
fn report_kind(&self) -> Option<String> {
self.report_kind.clone()
}
fn report_context(&self) -> Option<serde_json::Value> {
self.report_context.clone()
}
}
pub(crate) async fn execute_sync_plan_and_reports<T>(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
plan_and_reports: Vec<T>,
) -> Result<Option<Response<Body>>, GatewayError>
where
T: LocalPlanAndReport,
{
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_sync(
state,
parts.uri.path(),
plan_and_report.plan().clone(),
trace_id,
decision,
plan_kind,
plan_and_report.report_kind(),
plan_and_report.report_context(),
)
.await?
{
mark_unused_local_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
Ok(None)
}
pub(crate) async fn execute_stream_plan_and_reports<T>(
state: &AppState,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
plan_and_reports: Vec<T>,
) -> Result<Option<Response<Body>>, GatewayError>
where
T: LocalPlanAndReport,
{
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_stream(
state,
plan_and_report.plan().clone(),
trace_id,
decision,
plan_kind,
plan_and_report.report_kind(),
plan_and_report.report_context(),
)
.await?
{
mark_unused_local_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
Ok(None)
}
pub(crate) async fn mark_unused_local_candidates<T>(state: &AppState, remaining: Vec<T>)
where
T: LocalPlanAndReport,
{
for plan_and_report in remaining {
record_local_request_candidate_status(
state,
plan_and_report.plan(),
plan_and_report.report_context().as_ref(),
aether_data::repository::candidates::RequestCandidateStatus::Unused,
None,
None,
None,
None,
None,
None,
)
.await;
}
}
@@ -1,25 +0,0 @@
use axum::body::{Body, Bytes};
use axum::http::Response;
use crate::gateway::intent;
use crate::gateway::{AppState, GatewayControlDecision, GatewayError};
pub(crate) async fn maybe_execute_sync_local_path(
state: &AppState,
parts: &http::request::Parts,
body_bytes: &Bytes,
trace_id: &str,
decision: &GatewayControlDecision,
) -> Result<Option<Response<Body>>, GatewayError> {
intent::maybe_execute_via_sync_intent_path(state, parts, body_bytes, trace_id, decision).await
}
pub(crate) async fn maybe_execute_stream_local_path(
state: &AppState,
parts: &http::request::Parts,
body_bytes: &Bytes,
trace_id: &str,
decision: &GatewayControlDecision,
) -> Result<Option<Response<Body>>, GatewayError> {
intent::maybe_execute_via_stream_intent_path(state, parts, body_bytes, trace_id, decision).await
}
@@ -2,10 +2,7 @@ use crate::gateway::{AppState, GatewayControlDecision, GatewayError};
pub(crate) mod candidate_affinity;
pub(crate) mod common;
pub(crate) mod contracts;
mod decision;
pub(crate) mod family_core;
pub(crate) mod local_path;
pub(crate) mod passthrough;
pub(crate) mod plan_builders;
pub(crate) mod specialized;
@@ -26,7 +23,7 @@ pub(crate) use self::common::{
OPENAI_VIDEO_CONTENT_PLAN_KIND, OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
OPENAI_VIDEO_DELETE_SYNC_PLAN_KIND, OPENAI_VIDEO_REMIX_SYNC_PLAN_KIND,
};
pub(crate) use self::contracts::{
pub(crate) use crate::gateway::ai_pipeline::contracts::{
build_gateway_plan_request, generic_decision_missing_exact_provider_request,
GatewayControlPlanRequest, GatewayControlPlanResponse, GatewayControlSyncDecisionResponse,
};
@@ -43,18 +40,12 @@ pub(crate) use crate::gateway::scheduler::{
pub(crate) use passthrough::{
maybe_build_stream_local_same_format_provider_decision_payload,
maybe_build_sync_local_same_format_provider_decision_payload,
maybe_execute_stream_via_local_same_format_provider_decision,
maybe_execute_sync_via_local_same_format_provider_decision,
};
pub(crate) use specialized::{
maybe_build_stream_local_gemini_files_decision_payload,
maybe_build_sync_local_gemini_files_decision_payload,
maybe_build_sync_local_video_decision_payload,
maybe_execute_stream_via_local_gemini_files_decision,
maybe_execute_sync_via_local_gemini_files_decision,
maybe_execute_sync_via_local_video_decision,
};
pub(crate) use local_path::{maybe_execute_stream_local_path, maybe_execute_sync_local_path};
pub(crate) use standard::{
copy_request_number_field, copy_request_number_field_as,
map_openai_reasoning_effort_to_claude_output, map_openai_reasoning_effort_to_gemini_budget,
@@ -62,11 +53,7 @@ pub(crate) use standard::{
maybe_build_stream_local_openai_cli_decision_payload,
maybe_build_stream_local_standard_decision_payload, maybe_build_sync_local_decision_payload,
maybe_build_sync_local_openai_cli_decision_payload,
maybe_build_sync_local_standard_decision_payload, maybe_execute_stream_via_local_decision,
maybe_execute_stream_via_local_openai_cli_decision,
maybe_execute_stream_via_local_standard_decision, maybe_execute_sync_via_local_decision,
maybe_execute_sync_via_local_openai_cli_decision,
maybe_execute_sync_via_local_standard_decision, parse_openai_stop_sequences,
maybe_build_sync_local_standard_decision_payload, parse_openai_stop_sequences,
resolve_openai_chat_max_tokens, value_as_u64,
};
@@ -1,11 +1,9 @@
//! Requests that can stay in the same public/provider contract family.
mod provider;
pub(crate) mod provider;
pub(crate) use self::provider::{
maybe_build_stream_local_same_format_provider_decision_payload,
maybe_build_sync_local_same_format_provider_decision_payload,
maybe_execute_stream_via_local_same_format_provider_decision,
maybe_execute_sync_via_local_same_format_provider_decision,
};
pub(crate) use crate::gateway::provider_transport::provider_type_supports_local_same_format_transport;
@@ -8,6 +8,13 @@ use serde_json::{json, Value};
use tracing::warn;
use uuid::Uuid;
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
};
use crate::gateway::headers::collect_control_headers;
use crate::gateway::provider_transport::{
apply_local_body_rules, apply_local_header_rules, build_antigravity_safe_v1internal_request,
@@ -29,27 +36,17 @@ use crate::gateway::provider_transport::{
AntigravityRequestEnvelopeSupport, AntigravityRequestSideSupport, AntigravityRequestUrlAction,
LocalResolvedOAuthRequestAuth, KIRO_ENVELOPE_NAME,
};
use crate::gateway::request_candidates::{
current_unix_secs, record_local_request_candidate_status,
};
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::scheduler::{
list_selectable_candidates, GatewayMinimalCandidateSelectionCandidate,
current_unix_secs, list_selectable_candidates, record_local_request_candidate_status,
GatewayMinimalCandidateSelectionCandidate,
};
use crate::gateway::{
append_execution_contract_fields_to_value, execute_execution_runtime_stream,
execute_execution_runtime_sync, AppState, ConversionMode, ExecutionStrategy,
append_execution_contract_fields_to_value, AppState, ConversionMode, ExecutionStrategy,
GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
};
mod family;
mod plans;
pub(crate) mod family;
pub(crate) mod plans;
mod request;
pub(super) use self::family::{
@@ -61,12 +58,6 @@ pub(super) use self::family::{
pub(crate) use self::family::{
maybe_build_stream_local_same_format_provider_decision_payload,
maybe_build_sync_local_same_format_provider_decision_payload,
maybe_execute_stream_via_local_same_format_provider_decision,
maybe_execute_sync_via_local_same_format_provider_decision,
};
use self::plans::{
build_local_stream_plan_and_reports, build_local_sync_plan_and_reports, resolve_stream_spec,
resolve_sync_spec,
};
use self::request::{
build_same_format_provider_request_body, build_same_format_upstream_url,
@@ -1,69 +1,14 @@
use axum::body::Body;
use axum::http::Response;
use crate::gateway::{
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
use crate::gateway::ai_pipeline::planner::family_core::{
execute_stream_plan_and_reports, execute_sync_plan_and_reports,
};
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::{AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError};
use super::super::plans::{
build_local_stream_plan_and_reports, build_local_sync_plan_and_reports, resolve_stream_spec,
resolve_sync_spec,
};
use super::super::plans::{resolve_stream_spec, resolve_sync_spec};
use super::candidates::{
materialize_local_same_format_provider_candidate_attempts,
resolve_local_same_format_provider_decision_input,
};
use super::payload::maybe_build_local_same_format_provider_decision_payload_for_candidate;
pub(crate) async fn maybe_execute_sync_via_local_same_format_provider_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(spec) = resolve_sync_spec(plan_kind) else {
return Ok(None);
};
let plan_and_reports =
build_local_sync_plan_and_reports(state, parts, trace_id, decision, body_json, spec)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
execute_sync_plan_and_reports(state, parts, trace_id, decision, plan_kind, plan_and_reports)
.await
}
pub(crate) async fn maybe_execute_stream_via_local_same_format_provider_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(spec) = resolve_stream_spec(plan_kind) else {
return Ok(None);
};
let plan_and_reports =
build_local_stream_plan_and_reports(state, parts, trace_id, decision, body_json, spec)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
execute_stream_plan_and_reports(state, trace_id, decision, plan_kind, plan_and_reports).await
}
pub(crate) async fn maybe_build_sync_local_same_format_provider_decision_payload(
state: &AppState,
parts: &http::request::Parts,
@@ -3,8 +3,7 @@ use serde_json::json;
use tracing::warn;
use uuid::Uuid;
use crate::gateway::request_candidates::current_unix_secs;
use crate::gateway::scheduler::list_selectable_candidates;
use crate::gateway::scheduler::{current_unix_secs, list_selectable_candidates};
use crate::gateway::{
append_execution_contract_fields_to_value, AppState, ConversionMode, ExecutionStrategy,
GatewayControlDecision, GatewayError,
@@ -84,9 +83,10 @@ pub(crate) async fn materialize_local_same_format_provider_candidate_attempts(
current_unix_secs(),
)
.await?;
let candidates =
crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates(state, candidates)
.await;
let candidates = crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates(
state, candidates,
)
.await;
let created_at_unix_secs = current_unix_secs();
let mut attempts = Vec::with_capacity(candidates.len());
@@ -1,17 +1,15 @@
mod build;
mod candidates;
mod execute;
mod payload;
mod types;
pub(crate) use self::build::{
maybe_build_stream_local_same_format_provider_decision_payload,
maybe_build_sync_local_same_format_provider_decision_payload,
};
pub(crate) use self::candidates::{
materialize_local_same_format_provider_candidate_attempts,
resolve_local_same_format_provider_decision_input,
};
pub(crate) use self::execute::{
maybe_build_stream_local_same_format_provider_decision_payload,
maybe_build_sync_local_same_format_provider_decision_payload,
maybe_execute_stream_via_local_same_format_provider_decision,
maybe_execute_sync_via_local_same_format_provider_decision,
};
pub(crate) use self::payload::maybe_build_local_same_format_provider_decision_payload_for_candidate;
pub(crate) use self::types::{LocalSameFormatProviderFamily, LocalSameFormatProviderSpec};
@@ -21,8 +21,7 @@ use crate::gateway::provider_transport::{
AntigravityRequestEnvelopeSupport, AntigravityRequestSideSupport,
LocalResolvedOAuthRequestAuth, KIRO_ENVELOPE_NAME,
};
use crate::gateway::request_candidates::current_unix_secs;
use crate::gateway::scheduler::GatewayMinimalCandidateSelectionCandidate;
use crate::gateway::scheduler::{current_unix_secs, GatewayMinimalCandidateSelectionCandidate};
use crate::gateway::{
append_execution_contract_fields_to_value, AppState, ConversionMode, ExecutionStrategy,
GatewayControlSyncDecisionResponse, EXECUTION_RUNTIME_STREAM_DECISION_ACTION,
@@ -17,7 +17,7 @@ use crate::gateway::ai_pipeline::planner::{
GEMINI_CLI_STREAM_PLAN_KIND, GEMINI_CLI_SYNC_PLAN_KIND,
};
pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec> {
pub(crate) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec> {
match plan_kind {
CLAUDE_CHAT_SYNC_PLAN_KIND => Some(LocalSameFormatProviderSpec {
api_format: "claude:chat",
@@ -51,7 +51,7 @@ pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalSameFormatProvid
}
}
pub(super) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec> {
pub(crate) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalSameFormatProviderSpec> {
match plan_kind {
CLAUDE_CHAT_STREAM_PLAN_KIND => Some(LocalSameFormatProviderSpec {
api_format: "claude:chat",
@@ -85,7 +85,7 @@ pub(super) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalSameFormatProv
}
}
pub(super) async fn build_local_sync_plan_and_reports(
pub(crate) async fn build_local_sync_plan_and_reports(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
@@ -141,7 +141,7 @@ pub(super) async fn build_local_sync_plan_and_reports(
Ok(plans)
}
pub(super) async fn build_local_stream_plan_and_reports(
pub(crate) async fn build_local_stream_plan_and_reports(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
@@ -1,12 +1,20 @@
use std::collections::BTreeMap;
use aether_data::repository::candidates::{RequestCandidateStatus, UpsertRequestCandidateRecord};
use axum::body::Body;
use axum::http::Response;
use serde_json::json;
use tracing::warn;
use uuid::Uuid;
use crate::gateway::ai_pipeline::planner::plan_builders::{
build_passthrough_stream_plan_from_decision, build_passthrough_sync_plan_from_decision,
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
GEMINI_FILES_DELETE_PLAN_KIND, GEMINI_FILES_DOWNLOAD_PLAN_KIND, GEMINI_FILES_GET_PLAN_KIND,
GEMINI_FILES_LIST_PLAN_KIND, GEMINI_FILES_UPLOAD_PLAN_KIND,
};
use crate::gateway::headers::collect_control_headers;
use crate::gateway::provider_transport::{
apply_local_body_rules, apply_local_header_rules, build_gemini_files_passthrough_url,
@@ -14,26 +22,12 @@ use crate::gateway::provider_transport::{
resolve_transport_execution_timeouts, resolve_transport_proxy_snapshot_with_tunnel_affinity,
resolve_transport_tls_profile, supports_local_gemini_transport_with_network,
};
use crate::gateway::request_candidates::{
current_unix_secs, record_local_request_candidate_status,
};
use crate::gateway::ai_pipeline::planner::plan_builders::{
build_passthrough_stream_plan_from_decision, build_passthrough_sync_plan_from_decision,
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::scheduler::{
list_selectable_candidates_for_required_capability_without_requested_model,
GatewayMinimalCandidateSelectionCandidate,
current_unix_secs, list_selectable_candidates_for_required_capability_without_requested_model,
record_local_request_candidate_status, GatewayMinimalCandidateSelectionCandidate,
};
use crate::gateway::{
execute_execution_runtime_stream, execute_execution_runtime_sync, AppState,
GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
GEMINI_FILES_DELETE_PLAN_KIND, GEMINI_FILES_DOWNLOAD_PLAN_KIND, GEMINI_FILES_GET_PLAN_KIND,
GEMINI_FILES_LIST_PLAN_KIND, GEMINI_FILES_UPLOAD_PLAN_KIND,
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
const GEMINI_FILES_CANDIDATE_API_FORMAT: &str = "gemini:chat";
@@ -60,7 +54,7 @@ struct LocalGeminiFilesCandidateAttempt {
candidate_id: String,
}
pub(crate) async fn maybe_execute_sync_via_local_gemini_files_decision(
pub(crate) async fn build_local_gemini_files_sync_plan_and_reports_for_kind(
state: &AppState,
parts: &http::request::Parts,
body_json: &serde_json::Value,
@@ -69,12 +63,12 @@ pub(crate) async fn maybe_execute_sync_via_local_gemini_files_decision(
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
) -> Result<Vec<LocalSyncPlanAndReport>, GatewayError> {
let Some(spec) = resolve_sync_spec(plan_kind) else {
return Ok(None);
return Ok(Vec::new());
};
let plan_and_reports = build_local_sync_plan_and_reports(
build_local_sync_plan_and_reports(
state,
parts,
body_json,
@@ -84,69 +78,21 @@ pub(crate) async fn maybe_execute_sync_via_local_gemini_files_decision(
decision,
spec,
)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_sync(
state,
parts.uri.path(),
plan_and_report.plan,
trace_id,
decision,
plan_kind,
plan_and_report.report_kind,
plan_and_report.report_context,
)
.await?
{
mark_unused_local_files_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
Ok(None)
.await
}
pub(crate) async fn maybe_execute_stream_via_local_gemini_files_decision(
pub(crate) async fn build_local_gemini_files_stream_plan_and_reports_for_kind(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
) -> Result<Vec<LocalStreamPlanAndReport>, GatewayError> {
let Some(spec) = resolve_stream_spec(plan_kind) else {
return Ok(None);
return Ok(Vec::new());
};
let plan_and_reports =
build_local_stream_plan_and_reports(state, parts, trace_id, decision, spec).await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_stream(
state,
plan_and_report.plan,
trace_id,
decision,
plan_kind,
plan_and_report.report_kind,
plan_and_report.report_context,
)
.await?
{
mark_unused_local_files_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
Ok(None)
build_local_stream_plan_and_reports(state, parts, trace_id, decision, spec).await
}
pub(crate) async fn maybe_build_sync_local_gemini_files_decision_payload(
@@ -1,14 +1,10 @@
//! Non-matrix AI surfaces such as files and video.
mod files;
mod video;
pub(crate) mod files;
pub(crate) mod video;
pub(crate) use self::files::{
maybe_build_stream_local_gemini_files_decision_payload,
maybe_build_sync_local_gemini_files_decision_payload,
maybe_execute_stream_via_local_gemini_files_decision,
maybe_execute_sync_via_local_gemini_files_decision,
};
pub(crate) use self::video::{
maybe_build_sync_local_video_decision_payload, maybe_execute_sync_via_local_video_decision,
};
pub(crate) use self::video::maybe_build_sync_local_video_decision_payload;
@@ -1,12 +1,18 @@
use std::collections::BTreeMap;
use aether_data::repository::candidates::{RequestCandidateStatus, UpsertRequestCandidateRecord};
use axum::body::Body;
use axum::http::Response;
use serde_json::{json, Value};
use tracing::warn;
use uuid::Uuid;
use crate::gateway::ai_pipeline::planner::plan_builders::{
build_passthrough_sync_plan_from_decision, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_SYNC_DECISION_ACTION, GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND,
OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
};
use crate::gateway::headers::collect_control_headers;
use crate::gateway::provider_transport::{
apply_local_body_rules, apply_local_header_rules, build_gemini_video_predict_long_running_url,
@@ -15,23 +21,12 @@ use crate::gateway::provider_transport::{
resolve_transport_proxy_snapshot_with_tunnel_affinity, resolve_transport_tls_profile,
supports_local_gemini_transport_with_network, supports_local_standard_transport_with_network,
};
use crate::gateway::request_candidates::{
current_unix_secs, record_local_request_candidate_status,
};
use crate::gateway::ai_pipeline::planner::plan_builders::{
build_passthrough_sync_plan_from_decision, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::scheduler::{
list_selectable_candidates, GatewayMinimalCandidateSelectionCandidate,
current_unix_secs, list_selectable_candidates, record_local_request_candidate_status,
GatewayMinimalCandidateSelectionCandidate,
};
use crate::gateway::{
execute_execution_runtime_sync, AppState, GatewayControlDecision,
GatewayControlSyncDecisionResponse, GatewayError,
};
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_SYNC_DECISION_ACTION, GEMINI_VIDEO_CREATE_SYNC_PLAN_KIND,
OPENAI_VIDEO_CREATE_SYNC_PLAN_KIND,
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -62,45 +57,19 @@ struct LocalVideoCreateCandidateAttempt {
candidate_id: String,
}
pub(crate) async fn maybe_execute_sync_via_local_video_decision(
pub(crate) async fn build_local_video_sync_plan_and_reports_for_kind(
state: &AppState,
parts: &http::request::Parts,
body_json: &serde_json::Value,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
) -> Result<Vec<LocalSyncPlanAndReport>, GatewayError> {
let Some(spec) = resolve_sync_spec(plan_kind) else {
return Ok(None);
return Ok(Vec::new());
};
let plan_and_reports =
build_local_sync_plan_and_reports(state, parts, body_json, trace_id, decision, spec)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_sync(
state,
parts.uri.path(),
plan_and_report.plan,
trace_id,
decision,
plan_kind,
plan_and_report.report_kind,
plan_and_report.report_context,
)
.await?
{
mark_unused_local_video_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
Ok(None)
build_local_sync_plan_and_reports(state, parts, body_json, trace_id, decision, spec).await
}
pub(crate) async fn maybe_build_sync_local_video_decision_payload(
@@ -4,7 +4,7 @@ use crate::gateway::ai_pipeline::planner::{
use super::super::family::{LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec};
pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
pub(crate) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
match plan_kind {
CLAUDE_CHAT_SYNC_PLAN_KIND => Some(LocalStandardSpec {
api_format: "claude:chat",
@@ -18,7 +18,7 @@ pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
}
}
pub(super) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
pub(crate) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
match plan_kind {
CLAUDE_CHAT_STREAM_PLAN_KIND => Some(LocalStandardSpec {
api_format: "claude:chat",
@@ -4,7 +4,7 @@ use crate::gateway::ai_pipeline::planner::{
use super::super::family::{LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec};
pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
pub(crate) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
match plan_kind {
CLAUDE_CLI_SYNC_PLAN_KIND => Some(LocalStandardSpec {
api_format: "claude:cli",
@@ -18,7 +18,7 @@ pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
}
}
pub(super) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
pub(crate) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
match plan_kind {
CLAUDE_CLI_STREAM_PLAN_KIND => Some(LocalStandardSpec {
api_format: "claude:cli",
@@ -1,63 +1,14 @@
use axum::body::Body;
use axum::http::Response;
use crate::gateway::{
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
use super::family::{
maybe_build_stream_via_standard_family_payload, maybe_build_sync_via_standard_family_payload,
maybe_execute_stream_via_standard_family_decision,
maybe_execute_sync_via_standard_family_decision,
};
pub(crate) use crate::gateway::ai_pipeline::conversion::request::normalize_claude_request_to_openai_chat_request;
mod chat;
mod cli;
pub(crate) async fn maybe_execute_sync_via_local_claude_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
maybe_execute_sync_via_standard_family_decision(
state,
parts,
trace_id,
decision,
body_json,
plan_kind,
|plan_kind| {
chat::resolve_sync_spec(plan_kind).or_else(|| cli::resolve_sync_spec(plan_kind))
},
)
.await
}
pub(crate) async fn maybe_execute_stream_via_local_claude_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
maybe_execute_stream_via_standard_family_decision(
state,
parts,
trace_id,
decision,
body_json,
plan_kind,
|plan_kind| {
chat::resolve_stream_spec(plan_kind).or_else(|| cli::resolve_stream_spec(plan_kind))
},
)
.await
}
pub(crate) mod chat;
pub(crate) mod cli;
pub(crate) async fn maybe_build_sync_local_claude_decision_payload(
state: &AppState,
@@ -1,16 +1,13 @@
use axum::body::Body;
use axum::http::Response;
use tracing::warn;
use crate::gateway::ai_pipeline::planner::family_core::{
execute_stream_plan_and_reports, execute_sync_plan_and_reports,
};
use crate::gateway::ai_pipeline::planner::plan_builders::{
build_gemini_stream_plan_from_decision, build_gemini_sync_plan_from_decision,
build_standard_stream_plan_from_decision, build_standard_sync_plan_from_decision,
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::{AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError};
use crate::gateway::{
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
use super::candidates::{
materialize_local_standard_candidate_attempts, resolve_local_standard_decision_input,
@@ -18,53 +15,6 @@ use super::candidates::{
use super::payload::maybe_build_local_standard_decision_payload_for_candidate;
use super::types::{LocalStandardSourceFamily, LocalStandardSpec};
pub(crate) async fn maybe_execute_sync_via_standard_family_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
resolve_sync_spec: fn(&str) -> Option<LocalStandardSpec>,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(spec) = resolve_sync_spec(plan_kind) else {
return Ok(None);
};
let plan_and_reports =
build_local_sync_plan_and_reports(state, parts, trace_id, decision, body_json, spec)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
execute_sync_plan_and_reports(state, parts, trace_id, decision, plan_kind, plan_and_reports)
.await
}
pub(crate) async fn maybe_execute_stream_via_standard_family_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
resolve_stream_spec: fn(&str) -> Option<LocalStandardSpec>,
) -> Result<Option<Response<Body>>, GatewayError> {
let Some(spec) = resolve_stream_spec(plan_kind) else {
return Ok(None);
};
let plan_and_reports =
build_local_stream_plan_and_reports(state, parts, trace_id, decision, body_json, spec)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
execute_stream_plan_and_reports(state, trace_id, decision, plan_kind, plan_and_reports).await
}
pub(crate) async fn maybe_build_sync_via_standard_family_payload(
state: &AppState,
parts: &http::request::Parts,
@@ -137,7 +87,7 @@ pub(crate) async fn maybe_build_stream_via_standard_family_payload(
Ok(None)
}
async fn build_local_sync_plan_and_reports(
pub(crate) async fn build_local_sync_plan_and_reports(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
@@ -186,7 +136,7 @@ async fn build_local_sync_plan_and_reports(
Ok(plans)
}
async fn build_local_stream_plan_and_reports(
pub(crate) async fn build_local_stream_plan_and_reports(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
@@ -5,9 +5,8 @@ use serde_json::json;
use tracing::warn;
use uuid::Uuid;
use crate::gateway::request_candidates::current_unix_secs;
use crate::gateway::scheduler::{
list_selectable_candidates, GatewayMinimalCandidateSelectionCandidate,
current_unix_secs, list_selectable_candidates, GatewayMinimalCandidateSelectionCandidate,
};
use crate::gateway::{
append_execution_contract_fields_to_value, AppState, ConversionMode, ExecutionStrategy,
@@ -118,9 +117,10 @@ pub(super) async fn materialize_local_standard_candidate_attempts(
}
}
}
let candidates =
crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates(state, candidates)
.await;
let candidates = crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates(
state, candidates,
)
.await;
let created_at_unix_secs = current_unix_secs();
let mut attempts = Vec::with_capacity(candidates.len());
@@ -1,12 +1,11 @@
mod build;
mod candidates;
mod execute;
mod payload;
mod types;
pub(crate) use self::execute::{
pub(crate) use self::build::{
build_local_stream_plan_and_reports, build_local_sync_plan_and_reports,
maybe_build_stream_via_standard_family_payload, maybe_build_sync_via_standard_family_payload,
maybe_execute_stream_via_standard_family_decision,
maybe_execute_sync_via_standard_family_decision,
};
pub(crate) use self::types::{
LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec,
@@ -10,7 +10,7 @@ use crate::gateway::provider_transport::{
resolve_transport_execution_timeouts, resolve_transport_proxy_snapshot_with_tunnel_affinity,
resolve_transport_tls_profile, LocalResolvedOAuthRequestAuth,
};
use crate::gateway::request_candidates::current_unix_secs;
use crate::gateway::scheduler::current_unix_secs;
use crate::gateway::{
append_execution_contract_fields_to_value, AppState, ConversionMode, ExecutionStrategy,
GatewayControlSyncDecisionResponse, EXECUTION_RUNTIME_STREAM_DECISION_ACTION,
@@ -4,7 +4,7 @@ use crate::gateway::ai_pipeline::planner::{
use super::super::family::{LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec};
pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
pub(crate) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
match plan_kind {
GEMINI_CHAT_SYNC_PLAN_KIND => Some(LocalStandardSpec {
api_format: "gemini:chat",
@@ -18,7 +18,7 @@ pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
}
}
pub(super) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
pub(crate) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
match plan_kind {
GEMINI_CHAT_STREAM_PLAN_KIND => Some(LocalStandardSpec {
api_format: "gemini:chat",
@@ -4,7 +4,7 @@ use crate::gateway::ai_pipeline::planner::{
use super::super::family::{LocalStandardSourceFamily, LocalStandardSourceMode, LocalStandardSpec};
pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
pub(crate) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
match plan_kind {
GEMINI_CLI_SYNC_PLAN_KIND => Some(LocalStandardSpec {
api_format: "gemini:cli",
@@ -18,7 +18,7 @@ pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
}
}
pub(super) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
pub(crate) fn resolve_stream_spec(plan_kind: &str) -> Option<LocalStandardSpec> {
match plan_kind {
GEMINI_CLI_STREAM_PLAN_KIND => Some(LocalStandardSpec {
api_format: "gemini:cli",
@@ -1,63 +1,14 @@
use axum::body::Body;
use axum::http::Response;
use crate::gateway::{
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
use super::family::{
maybe_build_stream_via_standard_family_payload, maybe_build_sync_via_standard_family_payload,
maybe_execute_stream_via_standard_family_decision,
maybe_execute_sync_via_standard_family_decision,
};
pub(crate) use crate::gateway::ai_pipeline::conversion::request::normalize_gemini_request_to_openai_chat_request;
mod chat;
mod cli;
pub(crate) async fn maybe_execute_sync_via_local_gemini_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
maybe_execute_sync_via_standard_family_decision(
state,
parts,
trace_id,
decision,
body_json,
plan_kind,
|plan_kind| {
chat::resolve_sync_spec(plan_kind).or_else(|| cli::resolve_sync_spec(plan_kind))
},
)
.await
}
pub(crate) async fn maybe_execute_stream_via_local_gemini_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
maybe_execute_stream_via_standard_family_decision(
state,
parts,
trace_id,
decision,
body_json,
plan_kind,
|plan_kind| {
chat::resolve_stream_spec(plan_kind).or_else(|| cli::resolve_stream_spec(plan_kind))
},
)
.await
}
pub(crate) mod chat;
pub(crate) mod cli;
pub(crate) async fn maybe_build_sync_local_gemini_decision_payload(
state: &AppState,
@@ -1,88 +1,51 @@
//! Standard contract planning surface.
//!
//! This groups the public standard matrix in one place:
//! request-side conversion, matrix registry, and local standard execution entrypoints.
use axum::body::Body;
use axum::http::Response;
//! This groups the standard planning surface in one place:
//! request-side conversion, matrix registry, and decision payload builders.
use crate::gateway::{
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
pub(crate) mod claude;
mod family;
pub(crate) mod family;
pub(crate) mod gemini;
mod matrix;
pub(crate) mod openai;
mod normalize;
pub(crate) mod openai_chat;
pub(crate) mod openai_cli;
pub(crate) use self::matrix::{
build_standard_request_body, build_standard_upstream_url,
normalize_standard_request_to_openai_chat_request,
};
pub(crate) use self::normalize::{
build_cross_format_openai_chat_request_body, build_cross_format_openai_chat_upstream_url,
build_cross_format_openai_cli_request_body, build_cross_format_openai_cli_upstream_url,
build_local_openai_chat_request_body, build_local_openai_chat_upstream_url,
build_local_openai_cli_request_body, build_local_openai_cli_upstream_url,
};
pub(crate) use self::openai_chat::{
copy_request_number_field, copy_request_number_field_as,
map_openai_reasoning_effort_to_claude_output, map_openai_reasoning_effort_to_gemini_budget,
maybe_build_stream_local_decision_payload, maybe_build_sync_local_decision_payload,
parse_openai_stop_sequences, resolve_openai_chat_max_tokens, value_as_u64,
};
pub(crate) use self::openai_cli::{
maybe_build_stream_local_openai_cli_decision_payload,
maybe_build_sync_local_openai_cli_decision_payload,
};
pub(crate) use crate::gateway::ai_pipeline::conversion::request::{
convert_openai_chat_request_to_claude_request, convert_openai_chat_request_to_gemini_request,
convert_openai_chat_request_to_openai_cli_request, extract_openai_text_content,
normalize_openai_cli_request_to_openai_chat_request, parse_openai_tool_result_content,
};
pub(crate) use crate::gateway::ai_pipeline::conversion::{
build_core_error_body_for_client_format, request_conversion_kind,
request_conversion_transport_supported, sync_chat_response_conversion_kind,
sync_cli_response_conversion_kind, RequestConversionKind, SyncChatResponseConversionKind,
SyncCliResponseConversionKind,
};
pub(crate) use self::matrix::{
build_standard_request_body, build_standard_upstream_url,
normalize_standard_request_to_openai_chat_request,
};
pub(crate) use self::openai::{
copy_request_number_field, copy_request_number_field_as,
map_openai_reasoning_effort_to_claude_output, map_openai_reasoning_effort_to_gemini_budget,
maybe_build_stream_local_decision_payload, maybe_build_sync_local_decision_payload,
maybe_execute_stream_via_local_decision, maybe_execute_sync_via_local_decision,
parse_openai_stop_sequences, resolve_openai_chat_max_tokens, value_as_u64,
};
pub(crate) use self::openai::{
maybe_build_stream_local_openai_cli_decision_payload,
maybe_build_sync_local_openai_cli_decision_payload,
maybe_execute_stream_via_local_openai_cli_decision,
maybe_execute_sync_via_local_openai_cli_decision,
};
pub(crate) async fn maybe_execute_sync_via_local_standard_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
if let Some(response) = self::claude::maybe_execute_sync_via_local_claude_decision(
state, parts, trace_id, decision, body_json, plan_kind,
)
.await?
{
return Ok(Some(response));
}
self::gemini::maybe_execute_sync_via_local_gemini_decision(
state, parts, trace_id, decision, body_json, plan_kind,
)
.await
}
pub(crate) async fn maybe_execute_stream_via_local_standard_decision(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
if let Some(response) = self::claude::maybe_execute_stream_via_local_claude_decision(
state, parts, trace_id, decision, body_json, plan_kind,
)
.await?
{
return Ok(Some(response));
}
self::gemini::maybe_execute_stream_via_local_gemini_decision(
state, parts, trace_id, decision, body_json, plan_kind,
)
.await
}
pub(crate) async fn maybe_build_sync_local_standard_decision_payload(
state: &AppState,
@@ -11,10 +11,139 @@ use crate::gateway::ai_pipeline::conversion::request::{
use crate::gateway::ai_pipeline::conversion::{request_conversion_kind, RequestConversionKind};
use crate::gateway::provider_transport::{
apply_local_body_rules, build_antigravity_v1internal_url, build_claude_messages_url,
build_gemini_content_url, build_openai_cli_url, build_passthrough_path_url,
AntigravityRequestUrlAction,
build_gemini_content_url, build_openai_chat_url, build_openai_cli_url,
build_passthrough_path_url, AntigravityRequestUrlAction,
};
pub(crate) fn build_local_openai_chat_request_body(
body_json: &Value,
mapped_model: &str,
upstream_is_stream: bool,
body_rules: Option<&Value>,
) -> Option<Value> {
let request_body_object = body_json.as_object()?;
let mut provider_request_body = serde_json::Map::from_iter(
request_body_object
.iter()
.map(|(key, value)| (key.clone(), value.clone())),
);
provider_request_body.insert("model".to_string(), Value::String(mapped_model.to_string()));
if upstream_is_stream {
provider_request_body.insert("stream".to_string(), Value::Bool(true));
}
let mut provider_request_body = Value::Object(provider_request_body);
if !apply_local_body_rules(&mut provider_request_body, body_rules, Some(body_json)) {
return None;
}
Some(provider_request_body)
}
pub(crate) fn build_local_openai_chat_upstream_url(
parts: &http::request::Parts,
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
) -> Option<String> {
let custom_path = transport
.endpoint
.custom_path
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
match custom_path {
Some(path) => {
build_passthrough_path_url(&transport.endpoint.base_url, path, parts.uri.query(), &[])
}
None => Some(build_openai_chat_url(
&transport.endpoint.base_url,
parts.uri.query(),
)),
}
}
pub(crate) fn build_cross_format_openai_chat_request_body(
body_json: &Value,
mapped_model: &str,
provider_api_format: &str,
upstream_is_stream: bool,
body_rules: Option<&Value>,
) -> Option<Value> {
let conversion_kind = request_conversion_kind("openai:chat", provider_api_format)?;
let mut provider_request_body = match conversion_kind {
RequestConversionKind::ToClaudeStandard => convert_openai_chat_request_to_claude_request(
body_json,
mapped_model,
upstream_is_stream,
)?,
RequestConversionKind::ToGeminiStandard => convert_openai_chat_request_to_gemini_request(
body_json,
mapped_model,
upstream_is_stream,
)?,
RequestConversionKind::ToOpenAIFamilyCli => {
convert_openai_chat_request_to_openai_cli_request(
body_json,
mapped_model,
upstream_is_stream,
false,
)?
}
RequestConversionKind::ToOpenAICompact => {
convert_openai_chat_request_to_openai_cli_request(body_json, mapped_model, false, true)?
}
_ => return None,
};
if !apply_local_body_rules(&mut provider_request_body, body_rules, Some(body_json)) {
return None;
}
Some(provider_request_body)
}
pub(crate) fn build_cross_format_openai_chat_upstream_url(
parts: &http::request::Parts,
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
mapped_model: &str,
provider_api_format: &str,
upstream_is_stream: bool,
) -> Option<String> {
let conversion_kind = request_conversion_kind("openai:chat", provider_api_format)?;
let custom_path = transport
.endpoint
.custom_path
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
match custom_path {
Some(path) => {
build_passthrough_path_url(&transport.endpoint.base_url, path, parts.uri.query(), &[])
}
None => match conversion_kind {
RequestConversionKind::ToClaudeStandard => Some(build_claude_messages_url(
&transport.endpoint.base_url,
parts.uri.query(),
)),
RequestConversionKind::ToGeminiStandard => build_gemini_content_url(
&transport.endpoint.base_url,
mapped_model,
upstream_is_stream,
parts.uri.query(),
),
RequestConversionKind::ToOpenAIFamilyCli => Some(build_openai_cli_url(
&transport.endpoint.base_url,
parts.uri.query(),
false,
)),
RequestConversionKind::ToOpenAICompact => Some(build_openai_cli_url(
&transport.endpoint.base_url,
parts.uri.query(),
true,
)),
_ => None,
},
}
}
pub(crate) fn build_local_openai_cli_request_body(
body_json: &Value,
mapped_model: &str,
@@ -8,6 +8,14 @@ use uuid::Uuid;
use crate::gateway::ai_pipeline::conversion::{
request_conversion_direct_auth, request_conversion_kind, request_conversion_transport_supported,
};
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
OPENAI_CHAT_STREAM_PLAN_KIND,
};
use crate::gateway::headers::collect_control_headers;
use crate::gateway::provider_transport::{
apply_local_header_rules, build_openai_passthrough_headers, ensure_upstream_auth_header,
@@ -15,23 +23,16 @@ use crate::gateway::provider_transport::{
resolve_transport_proxy_snapshot_with_tunnel_affinity, resolve_transport_tls_profile,
supports_local_openai_chat_transport, LocalResolvedOAuthRequestAuth,
};
use crate::gateway::request_candidates::record_local_request_candidate_status;
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
use crate::gateway::scheduler::{
record_local_request_candidate_status, GatewayMinimalCandidateSelectionCandidate,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::scheduler::GatewayMinimalCandidateSelectionCandidate;
use crate::gateway::{
append_execution_contract_fields_to_value, AppState, ConversionMode, ExecutionStrategy,
GatewayControlSyncDecisionResponse,
};
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
OPENAI_CHAT_STREAM_PLAN_KIND,
};
use super::plans::current_unix_secs;
use crate::gateway::ai_pipeline::planner::standard::openai::{
use crate::gateway::ai_pipeline::planner::standard::{
build_cross_format_openai_chat_request_body, build_cross_format_openai_chat_upstream_url,
build_local_openai_chat_request_body, build_local_openai_chat_upstream_url,
};
@@ -13,12 +13,12 @@ use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::scheduler::{
list_selectable_candidates, GatewayMinimalCandidateSelectionCandidate,
};
use crate::gateway::ai_pipeline::planner::{
OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND,
};
use crate::gateway::scheduler::{
list_selectable_candidates, GatewayMinimalCandidateSelectionCandidate,
};
pub(super) fn build_local_openai_chat_miss_diagnostic(
decision: &GatewayControlDecision,
@@ -5,12 +5,19 @@ use serde_json::{json, Value};
use tracing::warn;
use uuid::Uuid;
use crate::gateway::ai_pipeline::planner::standard::openai::{
use crate::gateway::ai_pipeline::conversion::{
request_conversion_direct_auth, request_conversion_kind, request_conversion_transport_supported,
};
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::ai_pipeline::planner::standard::{
build_cross_format_openai_cli_request_body, build_cross_format_openai_cli_upstream_url,
build_local_openai_cli_request_body, build_local_openai_cli_upstream_url,
};
use crate::gateway::ai_pipeline::conversion::{
request_conversion_direct_auth, request_conversion_kind, request_conversion_transport_supported,
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
};
use crate::gateway::headers::collect_control_headers;
use crate::gateway::provider_transport::{
@@ -23,23 +30,14 @@ use crate::gateway::provider_transport::{
AntigravityRequestEnvelopeSupport, AntigravityRequestSideSupport,
LocalResolvedOAuthRequestAuth,
};
use crate::gateway::request_candidates::{
current_unix_secs, record_local_request_candidate_status,
};
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::planner::prefer_local_tunnel_owner_candidates;
use crate::gateway::scheduler::{
list_selectable_candidates, GatewayMinimalCandidateSelectionCandidate,
current_unix_secs, list_selectable_candidates, record_local_request_candidate_status,
GatewayMinimalCandidateSelectionCandidate,
};
use crate::gateway::{
append_execution_contract_fields_to_value, AppState, ConversionMode, ExecutionStrategy,
GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
use crate::gateway::ai_pipeline::planner::{
EXECUTION_RUNTIME_STREAM_DECISION_ACTION, EXECUTION_RUNTIME_SYNC_DECISION_ACTION,
};
const ANTIGRAVITY_ENVELOPE_NAME: &str = "antigravity:v1internal";
@@ -9,11 +9,11 @@ use crate::gateway::ai_pipeline::planner::plan_builders::{
build_openai_cli_stream_plan_from_decision, build_openai_cli_sync_plan_from_decision,
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::{AppState, GatewayControlDecision, GatewayError};
use crate::gateway::ai_pipeline::planner::{
OPENAI_CLI_STREAM_PLAN_KIND, OPENAI_CLI_SYNC_PLAN_KIND, OPENAI_COMPACT_STREAM_PLAN_KIND,
OPENAI_COMPACT_SYNC_PLAN_KIND,
};
use crate::gateway::{AppState, GatewayControlDecision, GatewayError};
pub(super) fn resolve_sync_spec(plan_kind: &str) -> Option<LocalOpenAiCliSpec> {
match plan_kind {
@@ -1,31 +0,0 @@
mod chat;
mod cli;
mod normalize_chat;
mod normalize_cli;
pub(crate) use crate::gateway::ai_pipeline::conversion::request::{
convert_openai_chat_request_to_claude_request, convert_openai_chat_request_to_gemini_request,
convert_openai_chat_request_to_openai_cli_request, extract_openai_text_content,
normalize_openai_cli_request_to_openai_chat_request, parse_openai_tool_result_content,
};
pub(crate) use self::chat::{
copy_request_number_field, copy_request_number_field_as,
map_openai_reasoning_effort_to_claude_output, map_openai_reasoning_effort_to_gemini_budget,
maybe_build_stream_local_decision_payload, maybe_build_sync_local_decision_payload,
maybe_execute_stream_via_local_decision, maybe_execute_sync_via_local_decision,
parse_openai_stop_sequences, resolve_openai_chat_max_tokens, value_as_u64,
};
pub(crate) use self::cli::{
maybe_build_stream_local_openai_cli_decision_payload,
maybe_build_sync_local_openai_cli_decision_payload,
maybe_execute_stream_via_local_openai_cli_decision,
maybe_execute_sync_via_local_openai_cli_decision,
};
pub(crate) use self::normalize_chat::{
build_cross_format_openai_chat_request_body, build_cross_format_openai_chat_upstream_url,
build_local_openai_chat_request_body, build_local_openai_chat_upstream_url,
};
pub(crate) use self::normalize_cli::{
build_cross_format_openai_cli_request_body, build_cross_format_openai_cli_upstream_url,
build_local_openai_cli_request_body, build_local_openai_cli_upstream_url,
};
@@ -1,5 +0,0 @@
mod request;
pub(crate) use self::request::{
build_cross_format_openai_chat_request_body, build_cross_format_openai_chat_upstream_url,
build_local_openai_chat_request_body, build_local_openai_chat_upstream_url,
};
@@ -1,143 +0,0 @@
use serde_json::Value;
use crate::gateway::ai_pipeline::conversion::request::{
convert_openai_chat_request_to_claude_request, convert_openai_chat_request_to_gemini_request,
convert_openai_chat_request_to_openai_cli_request,
};
use crate::gateway::ai_pipeline::conversion::{request_conversion_kind, RequestConversionKind};
use crate::gateway::provider_transport::{
apply_local_body_rules, build_claude_messages_url, build_gemini_content_url,
build_openai_chat_url, build_openai_cli_url, build_passthrough_path_url,
};
pub(crate) fn build_local_openai_chat_request_body(
body_json: &serde_json::Value,
mapped_model: &str,
upstream_is_stream: bool,
body_rules: Option<&serde_json::Value>,
) -> Option<serde_json::Value> {
let request_body_object = body_json.as_object()?;
let mut provider_request_body = serde_json::Map::from_iter(
request_body_object
.iter()
.map(|(key, value)| (key.clone(), value.clone())),
);
provider_request_body.insert(
"model".to_string(),
serde_json::Value::String(mapped_model.to_string()),
);
if upstream_is_stream {
provider_request_body.insert("stream".to_string(), serde_json::Value::Bool(true));
}
let mut provider_request_body = serde_json::Value::Object(provider_request_body);
if !apply_local_body_rules(&mut provider_request_body, body_rules, Some(body_json)) {
return None;
}
Some(provider_request_body)
}
pub(crate) fn build_local_openai_chat_upstream_url(
parts: &http::request::Parts,
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
) -> Option<String> {
let custom_path = transport
.endpoint
.custom_path
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
match custom_path {
Some(path) => {
build_passthrough_path_url(&transport.endpoint.base_url, path, parts.uri.query(), &[])
}
None => Some(build_openai_chat_url(
&transport.endpoint.base_url,
parts.uri.query(),
)),
}
}
pub(crate) fn build_cross_format_openai_chat_request_body(
body_json: &Value,
mapped_model: &str,
provider_api_format: &str,
upstream_is_stream: bool,
body_rules: Option<&Value>,
) -> Option<Value> {
let conversion_kind = request_conversion_kind("openai:chat", provider_api_format)?;
let mut provider_request_body = match conversion_kind {
RequestConversionKind::ToClaudeStandard => convert_openai_chat_request_to_claude_request(
body_json,
mapped_model,
upstream_is_stream,
)?,
RequestConversionKind::ToGeminiStandard => convert_openai_chat_request_to_gemini_request(
body_json,
mapped_model,
upstream_is_stream,
)?,
RequestConversionKind::ToOpenAIFamilyCli => {
convert_openai_chat_request_to_openai_cli_request(
body_json,
mapped_model,
upstream_is_stream,
false,
)?
}
RequestConversionKind::ToOpenAICompact => {
convert_openai_chat_request_to_openai_cli_request(body_json, mapped_model, false, true)?
}
_ => return None,
};
if !apply_local_body_rules(&mut provider_request_body, body_rules, Some(body_json)) {
return None;
}
Some(provider_request_body)
}
pub(crate) fn build_cross_format_openai_chat_upstream_url(
parts: &http::request::Parts,
transport: &crate::gateway::provider_transport::GatewayProviderTransportSnapshot,
mapped_model: &str,
provider_api_format: &str,
upstream_is_stream: bool,
) -> Option<String> {
let conversion_kind = request_conversion_kind("openai:chat", provider_api_format)?;
let custom_path = transport
.endpoint
.custom_path
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
match custom_path {
Some(path) => {
build_passthrough_path_url(&transport.endpoint.base_url, path, parts.uri.query(), &[])
}
None => match conversion_kind {
RequestConversionKind::ToClaudeStandard => Some(build_claude_messages_url(
&transport.endpoint.base_url,
parts.uri.query(),
)),
RequestConversionKind::ToGeminiStandard => build_gemini_content_url(
&transport.endpoint.base_url,
mapped_model,
upstream_is_stream,
parts.uri.query(),
),
RequestConversionKind::ToOpenAIFamilyCli => Some(build_openai_cli_url(
&transport.endpoint.base_url,
parts.uri.query(),
false,
)),
RequestConversionKind::ToOpenAICompact => Some(build_openai_cli_url(
&transport.endpoint.base_url,
parts.uri.query(),
true,
)),
_ => None,
},
}
}
@@ -5,7 +5,7 @@ use super::{
GatewayControlSyncDecisionResponse, GatewayError, LocalStreamPlanAndReport,
LocalSyncPlanAndReport,
};
use crate::gateway::ai_pipeline::private_surfaces::provider_adaptation_requires_eventstream_accept;
use crate::gateway::ai_pipeline::adaptation::surfaces::provider_adaptation_requires_eventstream_accept;
use crate::gateway::provider_transport::{
build_openai_chat_url, build_openai_cli_url, build_openai_passthrough_headers,
ensure_upstream_auth_header,
@@ -1,29 +1,27 @@
use axum::body::Body;
use axum::http::Response;
use serde_json::{Map, Value};
use tracing::warn;
use crate::gateway::{
execute_execution_runtime_stream, execute_execution_runtime_sync, AppState,
GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
LocalExecutionRuntimeMissDiagnostic,
};
use crate::gateway::ai_pipeline::planner::{
OPENAI_CHAT_STREAM_PLAN_KIND, OPENAI_CHAT_SYNC_PLAN_KIND,
};
use crate::gateway::{
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
LocalExecutionRuntimeMissDiagnostic,
};
use crate::gateway::ai_pipeline::planner::standard::openai::{
use crate::gateway::ai_pipeline::planner::standard::{
convert_openai_chat_request_to_claude_request, convert_openai_chat_request_to_gemini_request,
convert_openai_chat_request_to_openai_cli_request, extract_openai_text_content,
parse_openai_tool_result_content,
};
#[path = "openai/chat/decision.rs"]
mod decision;
#[path = "openai/chat/plans.rs"]
mod plans;
use self::decision::{
mark_unused_local_openai_chat_candidates, materialize_local_openai_chat_candidate_attempts,
materialize_local_openai_chat_candidate_attempts,
maybe_build_local_openai_chat_decision_payload_for_candidate, LocalOpenAiChatDecisionInput,
};
use self::plans::{
@@ -33,93 +31,48 @@ use self::plans::{
set_local_openai_chat_miss_diagnostic,
};
pub(crate) async fn maybe_execute_sync_via_local_decision(
pub(crate) async fn build_local_openai_chat_sync_plan_and_reports_for_kind(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
let plan_and_reports = build_local_openai_chat_sync_plan_and_reports(
) -> Result<
Vec<crate::gateway::ai_pipeline::planner::plan_builders::LocalSyncPlanAndReport>,
GatewayError,
> {
build_local_openai_chat_sync_plan_and_reports(
state, parts, trace_id, decision, body_json, plan_kind,
)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
let plan_count = plan_and_reports.len();
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_sync(
state,
parts.uri.path(),
plan_and_report.plan,
trace_id,
decision,
plan_kind,
plan_and_report.report_kind,
plan_and_report.report_context,
)
.await?
{
mark_unused_local_openai_chat_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
state.set_local_execution_runtime_miss_diagnostic(
trace_id,
LocalExecutionRuntimeMissDiagnostic {
candidate_count: Some(plan_count),
..build_local_openai_chat_miss_diagnostic(
decision,
plan_kind,
body_json.get("model").and_then(|value| value.as_str()),
"execution_runtime_candidates_exhausted",
)
},
);
Ok(None)
.await
}
pub(crate) async fn maybe_execute_stream_via_local_decision(
pub(crate) async fn build_local_openai_chat_stream_plan_and_reports_for_kind(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
let plan_and_reports = build_local_openai_chat_stream_plan_and_reports(
) -> Result<
Vec<crate::gateway::ai_pipeline::planner::plan_builders::LocalStreamPlanAndReport>,
GatewayError,
> {
build_local_openai_chat_stream_plan_and_reports(
state, parts, trace_id, decision, body_json, plan_kind,
)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
let plan_count = plan_and_reports.len();
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_stream(
state,
plan_and_report.plan,
trace_id,
decision,
plan_kind,
plan_and_report.report_kind,
plan_and_report.report_context,
)
.await?
{
mark_unused_local_openai_chat_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
.await
}
pub(crate) fn set_local_openai_chat_execution_exhausted_diagnostic(
state: &AppState,
trace_id: &str,
decision: &GatewayControlDecision,
plan_kind: &str,
body_json: &serde_json::Value,
plan_count: usize,
) {
state.set_local_execution_runtime_miss_diagnostic(
trace_id,
LocalExecutionRuntimeMissDiagnostic {
@@ -132,8 +85,6 @@ pub(crate) async fn maybe_execute_stream_via_local_decision(
)
},
);
Ok(None)
}
pub(crate) async fn maybe_build_sync_local_decision_payload(
@@ -1,15 +1,17 @@
use crate::gateway::ai_pipeline::planner::plan_builders::{
LocalStreamPlanAndReport, LocalSyncPlanAndReport,
};
use crate::gateway::{
execute_execution_runtime_stream, execute_execution_runtime_sync, AppState,
GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
AppState, GatewayControlDecision, GatewayControlSyncDecisionResponse, GatewayError,
};
use axum::body::Body;
use axum::http::Response;
#[path = "openai/cli/decision.rs"]
mod decision;
#[path = "openai/cli/plans.rs"]
mod plans;
use self::decision::{
mark_unused_local_openai_cli_candidates, materialize_local_openai_cli_candidate_attempts,
materialize_local_openai_cli_candidate_attempts,
maybe_build_local_openai_cli_decision_payload_for_candidate,
resolve_local_openai_cli_decision_input,
};
@@ -18,85 +20,34 @@ use self::plans::{
resolve_sync_spec,
};
pub(crate) async fn maybe_execute_sync_via_local_openai_cli_decision(
pub(crate) async fn build_local_openai_cli_sync_plan_and_reports_for_kind(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
) -> Result<Vec<LocalSyncPlanAndReport>, GatewayError> {
let Some(spec) = resolve_sync_spec(plan_kind) else {
return Ok(None);
return Ok(Vec::new());
};
let plan_and_reports =
build_local_sync_plan_and_reports(state, parts, trace_id, decision, body_json, spec)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_sync(
state,
parts.uri.path(),
plan_and_report.plan,
trace_id,
decision,
plan_kind,
plan_and_report.report_kind,
plan_and_report.report_context,
)
.await?
{
mark_unused_local_openai_cli_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
Ok(None)
build_local_sync_plan_and_reports(state, parts, trace_id, decision, body_json, spec).await
}
pub(crate) async fn maybe_execute_stream_via_local_openai_cli_decision(
pub(crate) async fn build_local_openai_cli_stream_plan_and_reports_for_kind(
state: &AppState,
parts: &http::request::Parts,
trace_id: &str,
decision: &GatewayControlDecision,
body_json: &serde_json::Value,
plan_kind: &str,
) -> Result<Option<Response<Body>>, GatewayError> {
) -> Result<Vec<LocalStreamPlanAndReport>, GatewayError> {
let Some(spec) = resolve_stream_spec(plan_kind) else {
return Ok(None);
return Ok(Vec::new());
};
let plan_and_reports =
build_local_stream_plan_and_reports(state, parts, trace_id, decision, body_json, spec)
.await?;
if plan_and_reports.is_empty() {
return Ok(None);
}
let mut remaining = plan_and_reports.into_iter();
while let Some(plan_and_report) = remaining.next() {
if let Some(response) = execute_execution_runtime_stream(
state,
plan_and_report.plan,
trace_id,
decision,
plan_kind,
plan_and_report.report_kind,
plan_and_report.report_context,
)
.await?
{
mark_unused_local_openai_cli_candidates(state, remaining.collect()).await;
return Ok(Some(response));
}
}
Ok(None)
build_local_stream_plan_and_reports(state, parts, trace_id, decision, body_json, spec).await
}
pub(crate) async fn maybe_build_sync_local_openai_cli_decision_payload(
@@ -2,7 +2,7 @@ use aether_contracts::RequestBody;
use super::augment_sync_report_context;
use super::*;
use crate::gateway::ai_pipeline::private_surfaces::provider_adaptation_requires_eventstream_accept;
use crate::gateway::ai_pipeline::adaptation::surfaces::provider_adaptation_requires_eventstream_accept;
use crate::gateway::provider_transport::ensure_upstream_auth_header;
pub(crate) fn build_standard_sync_plan_from_decision(