mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor: 移除 Python upstream 依赖,清理全部 legacy/Python 兼容层
- 移除 upstream_base_url 参数及 AETHER_GATEWAY_UPSTREAM 环境变量,gateway 不再需要指向 Python 宿主 - 删除所有 LEGACY_*/PYTHON_* 常量、路由组、header 定义及 sunset/phaseout 机制 - 将 legacy_gateway_bridge 重命名为 internal_gateway,executor 相关命名统一为 execution_runtime - dev.sh 新增 Postgres/Redis 预检查,移除 upstream 相关启动参数和提示 - 新增 ai_public 路由处理器 - 全量适配 handler、test、state、control 等模块的命名和接口变更
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
use super::util::crc32;
|
||||
use super::*;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use super::KiroToClaudeCliStreamState;
|
||||
|
||||
fn encode_string_header(name: &str, value: &str) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
out.push(name.len() as u8);
|
||||
|
||||
@@ -139,7 +139,14 @@ pub(crate) fn provider_adaptation_should_unwrap_stream_envelope(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::{
|
||||
provider_adaptation_allows_sync_finalize_envelope,
|
||||
provider_adaptation_anchor_api_format,
|
||||
provider_adaptation_requires_eventstream_accept,
|
||||
provider_adaptation_should_unwrap_stream_envelope,
|
||||
ANTIGRAVITY_V1INTERNAL_ENVELOPE_NAME, GEMINI_CLI_V1INTERNAL_ENVELOPE_NAME,
|
||||
KIRO_ENVELOPE_NAME,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn resolves_private_surface_anchor_contracts() {
|
||||
|
||||
@@ -151,7 +151,11 @@ fn is_standard_api_format(api_format: &str) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::{
|
||||
request_conversion_kind, sync_chat_response_conversion_kind,
|
||||
sync_cli_response_conversion_kind, RequestConversionKind, SyncChatResponseConversionKind,
|
||||
SyncCliResponseConversionKind,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn request_conversion_registry_supports_bidirectional_standard_matrix() {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use base64::Engine as _;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use super::*;
|
||||
use crate::gateway::ai_pipeline::finalize::common::{
|
||||
build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments,
|
||||
local_finalize_allows_envelope, parse_stream_json_events, unwrap_local_finalize_response_value,
|
||||
LocalCoreSyncFinalizeOutcome,
|
||||
};
|
||||
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ClaudeContentBlockState {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use base64::Engine as _;
|
||||
|
||||
use super::aggregate_claude_stream_sync_response;
|
||||
use super::*;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::gateway::ai_pipeline::finalize::common::{
|
||||
build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments,
|
||||
local_finalize_allows_envelope, unwrap_local_finalize_response_value,
|
||||
LocalCoreSyncFinalizeOutcome,
|
||||
};
|
||||
use crate::gateway::ai_pipeline::finalize::standard::build_openai_cli_response;
|
||||
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
|
||||
|
||||
pub(crate) fn maybe_build_local_claude_cli_stream_sync_response(
|
||||
trace_id: &str,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use base64::Engine as _;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use super::*;
|
||||
use crate::gateway::ai_pipeline::finalize::common::{
|
||||
build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments,
|
||||
local_finalize_allows_envelope, parse_stream_json_events, unwrap_local_finalize_response_value,
|
||||
LocalCoreSyncFinalizeOutcome,
|
||||
};
|
||||
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
|
||||
|
||||
pub(crate) fn maybe_build_local_gemini_stream_sync_response(
|
||||
trace_id: &str,
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use base64::Engine as _;
|
||||
|
||||
use super::aggregate_gemini_stream_sync_response;
|
||||
use super::*;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::gateway::ai_pipeline::finalize::common::{
|
||||
build_generated_tool_call_id, build_local_success_outcome, canonicalize_tool_arguments,
|
||||
local_finalize_allows_envelope, unwrap_local_finalize_response_value,
|
||||
LocalCoreSyncFinalizeOutcome,
|
||||
};
|
||||
use crate::gateway::ai_pipeline::finalize::standard::build_openai_cli_response;
|
||||
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
|
||||
|
||||
pub(crate) fn maybe_build_local_gemini_cli_stream_sync_response(
|
||||
trace_id: &str,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use base64::Engine as _;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
aggregate_claude_stream_sync_response, aggregate_gemini_stream_sync_response,
|
||||
aggregate_openai_cli_stream_sync_response, convert_claude_chat_response_to_openai_chat,
|
||||
@@ -13,6 +15,7 @@ use crate::gateway::ai_pipeline::finalize::common::{
|
||||
local_finalize_allows_envelope, unwrap_local_finalize_response_value,
|
||||
LocalCoreSyncFinalizeOutcome,
|
||||
};
|
||||
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct OpenAIChatChoiceState {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use base64::Engine as _;
|
||||
|
||||
use super::*;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use super::{
|
||||
aggregate_claude_stream_sync_response, aggregate_gemini_stream_sync_response,
|
||||
convert_claude_cli_response_to_openai_cli, convert_gemini_cli_response_to_openai_cli,
|
||||
@@ -11,6 +12,7 @@ use crate::gateway::ai_pipeline::finalize::common::{
|
||||
canonicalize_tool_arguments, local_finalize_allows_envelope,
|
||||
unwrap_local_finalize_response_value, LocalCoreSyncFinalizeOutcome,
|
||||
};
|
||||
use crate::gateway::{GatewayControlDecision, GatewayError, GatewaySyncReportRequest};
|
||||
|
||||
pub(crate) fn maybe_build_local_openai_cli_stream_sync_response(
|
||||
trace_id: &str,
|
||||
|
||||
@@ -103,7 +103,9 @@ mod tests {
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
prefer_local_tunnel_owner_candidates, GatewayMinimalCandidateSelectionCandidate, AppState,
|
||||
};
|
||||
use crate::gateway::tunnel::TunnelAttachmentRecord;
|
||||
use crate::gateway::GatewayDataState;
|
||||
|
||||
@@ -245,7 +247,7 @@ mod tests {
|
||||
.expect("local attachment should serialize"),
|
||||
),
|
||||
]);
|
||||
let state = AppState::new("http://127.0.0.1:1")
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(data_state)
|
||||
.with_tunnel_identity_for_tests("gateway-a", Some("http://gateway-a:8080"));
|
||||
@@ -274,7 +276,7 @@ mod tests {
|
||||
std::sync::Arc::new(provider_catalog),
|
||||
"development-key",
|
||||
);
|
||||
let state = AppState::new("http://127.0.0.1:1")
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(data_state)
|
||||
.with_tunnel_identity_for_tests("gateway-a", Some("http://gateway-a:8080"));
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
use super::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::Value;
|
||||
use url::form_urlencoded;
|
||||
|
||||
use super::{
|
||||
apply_local_body_rules, build_antigravity_v1internal_url, build_claude_code_messages_url,
|
||||
build_claude_messages_url, build_gemini_content_url,
|
||||
build_kiro_generate_assistant_response_url, build_kiro_provider_request_body,
|
||||
build_passthrough_path_url, build_vertex_api_key_gemini_content_url,
|
||||
resolve_local_vertex_api_key_query_auth, sanitize_claude_code_request_body,
|
||||
AntigravityRequestUrlAction, LocalSameFormatProviderFamily, LocalSameFormatProviderSpec,
|
||||
};
|
||||
|
||||
pub(super) fn build_same_format_provider_request_body(
|
||||
body_json: &Value,
|
||||
|
||||
@@ -156,7 +156,8 @@ fn build_openai_chat_request_body(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::build_standard_request_body;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn builds_openai_chat_request_from_claude_chat_source() {
|
||||
|
||||
@@ -306,7 +306,8 @@ pub(crate) fn build_cross_format_openai_cli_upstream_url(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::build_cross_format_openai_cli_request_body;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn builds_openai_family_cross_format_request_body_from_compact_source() {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use aether_contracts::RequestBody;
|
||||
use aether_contracts::{ExecutionPlan, RequestBody};
|
||||
|
||||
use super::augment_sync_report_context;
|
||||
use super::*;
|
||||
use super::{augment_sync_report_context, LocalStreamPlanAndReport, LocalSyncPlanAndReport};
|
||||
use crate::gateway::ai_pipeline::adaptation::surfaces::provider_adaptation_requires_eventstream_accept;
|
||||
use crate::gateway::ai_pipeline::planner::generic_decision_missing_exact_provider_request;
|
||||
use crate::gateway::provider_transport::ensure_upstream_auth_header;
|
||||
use crate::gateway::{GatewayControlSyncDecisionResponse, GatewayError};
|
||||
|
||||
pub(crate) fn build_standard_sync_plan_from_decision(
|
||||
_parts: &http::request::Parts,
|
||||
|
||||
@@ -415,7 +415,10 @@ fn truncate_body(body: &str) -> String {
|
||||
mod tests {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::*;
|
||||
use super::{IDC_AMZ_USER_AGENT, KiroOAuthRefreshAdapter};
|
||||
use crate::gateway::provider_transport::oauth_refresh::{
|
||||
LocalOAuthRefreshAdapter, LocalResolvedOAuthRequestAuth,
|
||||
};
|
||||
use crate::gateway::provider_transport::snapshot::{
|
||||
GatewayProviderTransportEndpoint, GatewayProviderTransportKey,
|
||||
GatewayProviderTransportProvider, GatewayProviderTransportSnapshot,
|
||||
@@ -426,6 +429,7 @@ mod tests {
|
||||
use axum::routing::any;
|
||||
use axum::{Json, Router};
|
||||
use http::StatusCode;
|
||||
use serde_json::{json, Value};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::gateway::{
|
||||
pub(crate) fn mount_internal_routes(router: Router<AppState>) -> Router<AppState> {
|
||||
router
|
||||
.route(
|
||||
"/api/internal/gateway/{*legacy_gateway_path}",
|
||||
"/api/internal/gateway/{*internal_gateway_path}",
|
||||
any(proxy_request),
|
||||
)
|
||||
.route(PROXY_TUNNEL_PATH, get(proxy_tunnel))
|
||||
|
||||
@@ -6,13 +6,9 @@ use axum::Router;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::gateway::constants::{
|
||||
FRONTDOOR_COMPAT_ROUTE_PATTERNS, FRONTDOOR_MANIFEST_PATH, FRONTDOOR_MANIFEST_VERSION,
|
||||
FRONTDOOR_REPLACEABLE_MIDDLEWARE_GROUPS, FRONTDOOR_REPLACEABLE_ROUTE_GROUPS,
|
||||
INTERNAL_FRONTDOOR_MANIFEST_PATH, LEGACY_GATEWAY_BRIDGE_PATH_PREFIXES,
|
||||
LEGACY_GATEWAY_BRIDGE_ROUTE_GROUPS, LEGACY_INTERNAL_GATEWAY_PHASEOUT_STATUS,
|
||||
LEGACY_INTERNAL_GATEWAY_SUNSET_DATE, LEGACY_INTERNAL_GATEWAY_SUNSET_HTTP_DATE,
|
||||
PYTHON_ONLY_MIDDLEWARE_GROUPS, PYTHON_ONLY_ROUTE_GROUPS, PYTHON_ONLY_RUNTIME_COMPONENTS,
|
||||
READYZ_PATH, RUST_FRONTDOOR_OWNED_ROUTE_PATTERNS,
|
||||
FRONTDOOR_MANIFEST_PATH, FRONTDOOR_MANIFEST_VERSION, INTERNAL_FRONTDOOR_MANIFEST_PATH,
|
||||
INTERNAL_GATEWAY_PATH_PREFIXES, INTERNAL_GATEWAY_ROUTE_GROUPS, READYZ_PATH,
|
||||
RUST_FRONTDOOR_OWNED_ROUTE_PATTERNS,
|
||||
};
|
||||
use crate::gateway::AppState;
|
||||
|
||||
@@ -100,27 +96,10 @@ pub(crate) async fn frontdoor_manifest(State(state): State<AppState>) -> impl In
|
||||
"trace_id_injection": true,
|
||||
"compatibility_proxy": true,
|
||||
},
|
||||
},
|
||||
"python_host_boundary": {
|
||||
"replaceable_shell": {
|
||||
"route_groups": FRONTDOOR_REPLACEABLE_ROUTE_GROUPS,
|
||||
"middleware_groups": FRONTDOOR_REPLACEABLE_MIDDLEWARE_GROUPS,
|
||||
"route_patterns": FRONTDOOR_COMPAT_ROUTE_PATTERNS,
|
||||
"status": "should_move_to_rust_frontdoor",
|
||||
},
|
||||
"python_only": {
|
||||
"route_groups": PYTHON_ONLY_ROUTE_GROUPS,
|
||||
"middleware_groups": PYTHON_ONLY_MIDDLEWARE_GROUPS,
|
||||
"runtime_components": PYTHON_ONLY_RUNTIME_COMPONENTS,
|
||||
"status": "remain_on_python_host",
|
||||
},
|
||||
"legacy_bridge": {
|
||||
"route_groups": LEGACY_GATEWAY_BRIDGE_ROUTE_GROUPS,
|
||||
"path_prefixes": LEGACY_GATEWAY_BRIDGE_PATH_PREFIXES,
|
||||
"status": LEGACY_INTERNAL_GATEWAY_PHASEOUT_STATUS,
|
||||
"sunset_date": LEGACY_INTERNAL_GATEWAY_SUNSET_DATE,
|
||||
"sunset_http_date": LEGACY_INTERNAL_GATEWAY_SUNSET_HTTP_DATE,
|
||||
"replacement": "public_proxy_or_local_rust_control_plane",
|
||||
"internal_gateway": {
|
||||
"route_groups": INTERNAL_GATEWAY_ROUTE_GROUPS,
|
||||
"path_prefixes": INTERNAL_GATEWAY_PATH_PREFIXES,
|
||||
"status": "rust_native_control_plane",
|
||||
},
|
||||
},
|
||||
"features": {
|
||||
|
||||
@@ -26,7 +26,6 @@ fn insert_execution_runtime_candidate_headers(
|
||||
decision: &GatewayControlDecision,
|
||||
) -> Result<(), GatewayError> {
|
||||
let value = execution_runtime_candidate_header_value(decision);
|
||||
insert_header_if_missing(headers, CONTROL_LEGACY_EXECUTION_RUNTIME_HEADER, value)?;
|
||||
insert_header_if_missing(headers, CONTROL_EXECUTION_RUNTIME_HEADER, value)
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn records_shadow_result_for_ai_public_response() {
|
||||
let repository = Arc::new(InMemoryShadowResultRepository::default());
|
||||
let state = AppState::new("http://127.0.0.1:18084")
|
||||
let state = AppState::new()
|
||||
.expect("app state should build")
|
||||
.with_shadow_result_data_writer_for_tests(repository.clone());
|
||||
let response = Response::builder()
|
||||
@@ -205,7 +205,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn merges_rust_and_python_shadow_samples_into_match() {
|
||||
let repository = Arc::new(InMemoryShadowResultRepository::default());
|
||||
let state = AppState::new("http://127.0.0.1:18084")
|
||||
let state = AppState::new()
|
||||
.expect("app state should build")
|
||||
.with_shadow_result_data_repository_for_tests(repository.clone());
|
||||
let response = Response::builder()
|
||||
|
||||
@@ -8,9 +8,7 @@ pub(crate) const FORWARDED_FOR_HEADER: &str = "x-forwarded-for";
|
||||
pub(crate) const FORWARDED_PROTO_HEADER: &str = "x-forwarded-proto";
|
||||
pub(crate) const GATEWAY_HEADER: &str = "x-aether-gateway";
|
||||
pub(crate) const EXECUTION_PATH_HEADER: &str = "x-aether-execution-path";
|
||||
pub(crate) const PYTHON_DEPENDENCY_REASON_HEADER: &str = "x-aether-python-dependency-reason";
|
||||
pub(crate) const LOCAL_LEGACY_EXECUTION_RUNTIME_MISS_REASON_HEADER: &str =
|
||||
"x-aether-local-executor-miss-reason";
|
||||
pub(crate) const DEPENDENCY_REASON_HEADER: &str = "x-aether-dependency-reason";
|
||||
pub(crate) const LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER: &str =
|
||||
"x-aether-local-execution-runtime-miss-reason";
|
||||
pub(crate) const TUNNEL_AFFINITY_FORWARDED_BY_HEADER: &str =
|
||||
@@ -18,28 +16,24 @@ pub(crate) const TUNNEL_AFFINITY_FORWARDED_BY_HEADER: &str =
|
||||
pub(crate) const TUNNEL_AFFINITY_OWNER_INSTANCE_HEADER: &str =
|
||||
"x-aether-tunnel-affinity-owner-instance-id";
|
||||
pub(crate) const EXECUTION_PATH_PUBLIC_PROXY_PASSTHROUGH: &str = "public_proxy_passthrough";
|
||||
pub(crate) const LEGACY_EXECUTION_PATH_PUBLIC_PROXY_AFTER_EXECUTION_RUNTIME_MISS: &str =
|
||||
"public_proxy_after_executor_miss";
|
||||
pub(crate) const EXECUTION_PATH_PUBLIC_PROXY_AFTER_EXECUTION_RUNTIME_MISS: &str =
|
||||
"public_proxy_after_execution_runtime_miss";
|
||||
pub(crate) const EXECUTION_PATH_LOCAL_PROXY_PASSTHROUGH_REMOVED: &str =
|
||||
"local_proxy_passthrough_removed";
|
||||
pub(crate) const EXECUTION_PATH_EXECUTION_RUNTIME_SYNC: &str = "execution_runtime_sync";
|
||||
pub(crate) const EXECUTION_PATH_EXECUTION_RUNTIME_STREAM: &str = "execution_runtime_stream";
|
||||
pub(crate) const EXECUTION_PATH_CONTROL_EXECUTE_SYNC: &str = "control_execute_sync";
|
||||
pub(crate) const EXECUTION_PATH_CONTROL_EXECUTE_STREAM: &str = "control_execute_stream";
|
||||
pub(crate) const LEGACY_EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS: &str = "local_executor_miss";
|
||||
pub(crate) const EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS: &str = "local_execution_runtime_miss";
|
||||
pub(crate) const EXECUTION_PATH_LOCAL_AUTH_DENIED: &str = "local_auth_denied";
|
||||
pub(crate) const EXECUTION_PATH_LOCAL_RATE_LIMITED: &str = "local_rate_limited";
|
||||
pub(crate) const EXECUTION_PATH_LOCAL_ROUTE_NOT_FOUND: &str = "local_route_not_found";
|
||||
pub(crate) const EXECUTION_PATH_LOCAL_OVERLOADED: &str = "local_overloaded";
|
||||
pub(crate) const EXECUTION_PATH_DISTRIBUTED_OVERLOADED: &str = "distributed_overloaded";
|
||||
pub(crate) const EXECUTION_PATH_LOCAL_AI_PUBLIC: &str = "local_ai_public";
|
||||
pub(crate) const CONTROL_ROUTE_CLASS_HEADER: &str = "x-aether-control-route-class";
|
||||
pub(crate) const CONTROL_ROUTE_FAMILY_HEADER: &str = "x-aether-control-route-family";
|
||||
pub(crate) const CONTROL_ROUTE_KIND_HEADER: &str = "x-aether-control-route-kind";
|
||||
pub(crate) const CONTROL_LEGACY_EXECUTION_RUNTIME_HEADER: &str =
|
||||
"x-aether-control-executor-candidate";
|
||||
pub(crate) const CONTROL_EXECUTION_RUNTIME_HEADER: &str =
|
||||
"x-aether-control-execution-runtime-candidate";
|
||||
pub(crate) const CONTROL_LEGACY_EXECUTION_RUNTIME_CANDIDATE_KEY: &str = "executor_candidate";
|
||||
pub(crate) const CONTROL_EXECUTION_RUNTIME_CANDIDATE_KEY: &str = "execution_runtime_candidate";
|
||||
pub(crate) const CONTROL_REQUEST_ID_HEADER: &str = "x-aether-control-request-id";
|
||||
pub(crate) const CONTROL_CANDIDATE_ID_HEADER: &str = "x-aether-control-candidate-id";
|
||||
@@ -48,14 +42,6 @@ pub(crate) const CONTROL_EXECUTED_HEADER: &str = "x-aether-control-executed";
|
||||
pub(crate) const CONTROL_ACTION_HEADER: &str = "x-aether-control-action";
|
||||
pub(crate) const CONTROL_ACTION_PROXY_PUBLIC: &str = "proxy_public";
|
||||
pub(crate) const CONTROL_EXECUTE_FALLBACK_HEADER: &str = "x-aether-control-execute-fallback";
|
||||
pub(crate) const LEGACY_INTERNAL_GATEWAY_HEADER: &str = "x-aether-legacy-internal-gateway";
|
||||
pub(crate) const LEGACY_INTERNAL_GATEWAY_PHASEOUT_STATUS: &str = "scheduled_for_removal";
|
||||
pub(crate) const LEGACY_INTERNAL_GATEWAY_SUNSET_DATE: &str = "2026-06-01";
|
||||
pub(crate) const LEGACY_INTERNAL_GATEWAY_SUNSET_HTTP_DATE: &str = "Mon, 01 Jun 2026 00:00:00 GMT";
|
||||
pub(crate) const LEGACY_INTERNAL_GATEWAY_PHASEOUT_HEADER: &str =
|
||||
"x-aether-legacy-internal-gateway-phaseout";
|
||||
pub(crate) const LEGACY_INTERNAL_GATEWAY_SUNSET_DATE_HEADER: &str =
|
||||
"x-aether-legacy-internal-gateway-sunset-date";
|
||||
pub(crate) const TRUSTED_AUTH_USER_ID_HEADER: &str = "x-aether-auth-user-id";
|
||||
pub(crate) const TRUSTED_AUTH_API_KEY_ID_HEADER: &str = "x-aether-auth-api-key-id";
|
||||
pub(crate) const TRUSTED_AUTH_BALANCE_HEADER: &str = "x-aether-auth-balance-remaining";
|
||||
@@ -69,47 +55,8 @@ pub(crate) const TRUSTED_RATE_LIMIT_PREFLIGHT_HEADER: &str = "x-aether-rate-limi
|
||||
|
||||
pub(crate) const FRONTDOOR_REPLACEABLE_ROUTE_GROUPS: &[&str] = &["frontdoor_compat_router"];
|
||||
pub(crate) const FRONTDOOR_REPLACEABLE_MIDDLEWARE_GROUPS: &[&str] = &["cors"];
|
||||
// These manifest/reporting inventories intentionally remain explicit instead of being generated
|
||||
// from api::ai::registry router mounts. The manifest describes operational compatibility surfaces
|
||||
// and wildcard ownership, which is related to but not identical to the concrete axum route list.
|
||||
pub(crate) const FRONTDOOR_COMPAT_ROUTE_PATTERNS: &[&str] = &[
|
||||
"/v1/chat/completions",
|
||||
"/v1/messages",
|
||||
"/v1/messages/count_tokens",
|
||||
"/v1/responses",
|
||||
"/v1/responses/compact",
|
||||
"/v1/videos*",
|
||||
"/v1/models/{model}:generateContent",
|
||||
"/v1/models/{model}:streamGenerateContent",
|
||||
"/v1/models/{model}:predictLongRunning",
|
||||
"/v1beta/models/{model}:generateContent",
|
||||
"/v1beta/models/{model}:streamGenerateContent",
|
||||
"/v1beta/models/{model}:predictLongRunning",
|
||||
"/v1beta/models/{model}/operations/{id}",
|
||||
"/v1beta/operations*",
|
||||
"/upload/v1beta/files",
|
||||
"/v1beta/files*",
|
||||
];
|
||||
pub(crate) const PYTHON_ONLY_ROUTE_GROUPS: &[&str] = &[
|
||||
"auth_router",
|
||||
"python_admin_router",
|
||||
"me_router",
|
||||
"wallet_router",
|
||||
"payment_router",
|
||||
"announcement_router",
|
||||
"dashboard_router",
|
||||
"python_public_support_router",
|
||||
"monitoring_router",
|
||||
"python_internal_router",
|
||||
];
|
||||
pub(crate) const PYTHON_ONLY_MIDDLEWARE_GROUPS: &[&str] = &["plugin_middleware"];
|
||||
pub(crate) const PYTHON_ONLY_RUNTIME_COMPONENTS: &[&str] = &[
|
||||
"python_host_lifespan",
|
||||
"plugin_and_module_bootstrap",
|
||||
"background_workers",
|
||||
];
|
||||
pub(crate) const LEGACY_GATEWAY_BRIDGE_ROUTE_GROUPS: &[&str] = &["legacy_gateway_bridge_router"];
|
||||
pub(crate) const LEGACY_GATEWAY_BRIDGE_PATH_PREFIXES: &[&str] = &["/api/internal/gateway"];
|
||||
pub(crate) const INTERNAL_GATEWAY_ROUTE_GROUPS: &[&str] = &["internal_gateway_router"];
|
||||
pub(crate) const INTERNAL_GATEWAY_PATH_PREFIXES: &[&str] = &["/api/internal/gateway"];
|
||||
pub(crate) const RUST_FRONTDOOR_OWNED_ROUTE_PATTERNS: &[&str] = &[
|
||||
FRONTDOOR_MANIFEST_PATH,
|
||||
INTERNAL_FRONTDOOR_MANIFEST_PATH,
|
||||
|
||||
@@ -400,7 +400,11 @@ pub(super) fn current_unix_secs() -> u64 {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::{
|
||||
build_auth_context_cache_key, extract_request_credentials, GatewayCredentialCarrier,
|
||||
GatewayPrimaryCredential, GatewayTrustedAdminHeaders, GatewayTrustedAuthHeaders,
|
||||
};
|
||||
use axum::http::{self, Uri};
|
||||
|
||||
fn uri(path: &str) -> Uri {
|
||||
path.parse().expect("uri should parse")
|
||||
|
||||
@@ -34,7 +34,7 @@ pub(super) fn derive_principal_candidate(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::{derive_principal_candidate, GatewayPrincipalCandidate};
|
||||
use crate::gateway::control::auth::types::{
|
||||
GatewayCredentialBundle, GatewayCredentialCarrier, GatewayExtractedCredentials,
|
||||
GatewayPrimaryCredential, GatewayTrustedAuthHeaders,
|
||||
|
||||
@@ -106,14 +106,14 @@ pub(in super::super) async fn resolve_control_decision_auth(
|
||||
return Ok(ControlDecisionAuthResolution::Resolved(decision));
|
||||
}
|
||||
|
||||
if skips_legacy_python_auth_context(&decision) {
|
||||
if allows_missing_data_backed_auth_context(&decision) {
|
||||
return Ok(ControlDecisionAuthResolution::Resolved(decision));
|
||||
}
|
||||
|
||||
Ok(ControlDecisionAuthResolution::Resolved(decision))
|
||||
}
|
||||
|
||||
fn skips_legacy_python_auth_context(decision: &GatewayControlDecision) -> bool {
|
||||
fn allows_missing_data_backed_auth_context(decision: &GatewayControlDecision) -> bool {
|
||||
matches!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("chat" | "cli" | "compact")
|
||||
@@ -593,7 +593,7 @@ mod tests {
|
||||
sample_snapshot("key-1", "user-1"),
|
||||
)]));
|
||||
let data = GatewayDataState::with_auth_api_key_repository_for_tests(repository.clone());
|
||||
let state = AppState::new("http://127.0.0.1:9")
|
||||
let state = AppState::new()
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(data);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
#[path = "admin/basic_families.rs"]
|
||||
mod basic_families;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_admin_basic_family_route(
|
||||
method: &http::Method,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_admin_endpoints_family_route(
|
||||
method: &http::Method,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_admin_model_provider_family_route(
|
||||
method: &http::Method,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_admin_observability_family_route(
|
||||
method: &http::Method,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_admin_operations_family_route(
|
||||
method: &http::Method,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_admin_provider_ops_routes(
|
||||
method: &http::Method,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use axum::http;
|
||||
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_admin_system_family_route(
|
||||
method: &http::Method,
|
||||
|
||||
@@ -18,11 +18,11 @@ pub(super) fn classify_internal_route(
|
||||
"/api/internal/gateway/finalize-sync" => "finalize_sync",
|
||||
"/api/internal/gateway/execute-sync" => "execute_sync",
|
||||
"/api/internal/gateway/execute-stream" => "execute_stream",
|
||||
_ => "legacy_gateway",
|
||||
_ => "unhandled",
|
||||
};
|
||||
Some(classified(
|
||||
"internal_proxy",
|
||||
"gateway_legacy",
|
||||
"internal_gateway",
|
||||
route_kind,
|
||||
"",
|
||||
false,
|
||||
|
||||
@@ -4,82 +4,7 @@ pub(super) fn classify_oauth_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET && normalized_path == "/api/oauth/providers" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_public_legacy",
|
||||
"providers",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path.starts_with("/api/oauth/") {
|
||||
if normalized_path.ends_with("/authorize") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_public_legacy",
|
||||
"authorize",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if normalized_path.ends_with("/callback") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_public_legacy",
|
||||
"callback",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if method == http::Method::GET && normalized_path == "/api/user/oauth/bindable-providers"
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"bindable_providers",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/user/oauth/links" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"links",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/user/oauth/")
|
||||
&& normalized_path.ends_with("/bind-token")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"bind_token",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/user/oauth/")
|
||||
&& normalized_path.ends_with("/bind")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"bind",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE && normalized_path.starts_with("/api/user/oauth/") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"unbind",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/oauth/supported-types" {
|
||||
if method == http::Method::GET && normalized_path == "/api/admin/oauth/supported-types" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"oauth_manage",
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
use super::{classified, is_gemini_models_route, is_gemini_operation_route, ClassifiedRoute};
|
||||
|
||||
fn has_single_segment_after_prefix(path: &str, prefix: &str) -> bool {
|
||||
let trimmed = path.trim_end_matches('/');
|
||||
let Some(segment) = trimmed.strip_prefix(prefix) else {
|
||||
return false;
|
||||
};
|
||||
!segment.is_empty() && !segment.contains('/')
|
||||
}
|
||||
|
||||
fn has_single_nested_suffix_after_prefix(path: &str, prefix: &str, suffix: &str) -> bool {
|
||||
let trimmed = path.trim_end_matches('/');
|
||||
let Some(rest) = trimmed.strip_prefix(prefix) else {
|
||||
return false;
|
||||
};
|
||||
let Some((segment, actual_suffix)) = rest.split_once('/') else {
|
||||
return false;
|
||||
};
|
||||
!segment.is_empty() && !segment.contains('/') && actual_suffix == suffix
|
||||
}
|
||||
|
||||
pub(super) fn classify_public_support_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
@@ -118,6 +137,7 @@ pub(super) fn classify_public_support_route(
|
||||
&& normalized_path != "/api/announcements/users/me/unread-count"
|
||||
&& normalized_path != "/api/announcements/users/me/unread-count/"
|
||||
&& !normalized_path.ends_with("/read-status")
|
||||
&& has_single_segment_after_prefix(normalized_path, "/api/announcements/")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
@@ -199,7 +219,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"auth_legacy",
|
||||
"auth",
|
||||
route_kind,
|
||||
"user:auth",
|
||||
false,
|
||||
@@ -222,7 +242,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"dashboard_legacy",
|
||||
"dashboard",
|
||||
route_kind,
|
||||
"user:dashboard",
|
||||
false,
|
||||
@@ -240,7 +260,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"monitoring_user_legacy",
|
||||
"monitoring_user",
|
||||
route_kind,
|
||||
"user:monitoring",
|
||||
false,
|
||||
@@ -254,7 +274,7 @@ pub(super) fn classify_public_support_route(
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcement_user_legacy",
|
||||
"announcement_user",
|
||||
"unread_count",
|
||||
"user:announcements",
|
||||
false,
|
||||
@@ -267,7 +287,7 @@ pub(super) fn classify_public_support_route(
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcement_user_legacy",
|
||||
"announcement_user",
|
||||
"read_all",
|
||||
"user:announcements",
|
||||
false,
|
||||
@@ -275,10 +295,15 @@ pub(super) fn classify_public_support_route(
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/announcements/")
|
||||
&& (normalized_path.ends_with("/read-status") || normalized_path.ends_with("/read-status/"))
|
||||
&& has_single_nested_suffix_after_prefix(
|
||||
normalized_path,
|
||||
"/api/announcements/",
|
||||
"read-status",
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcement_user_legacy",
|
||||
"announcement_user",
|
||||
"read_status",
|
||||
"user:announcements",
|
||||
false,
|
||||
@@ -305,23 +330,27 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"wallet_legacy",
|
||||
"wallet",
|
||||
route_kind,
|
||||
"user:wallet",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path.starts_with("/api/wallet/recharge/") {
|
||||
} else if method == http::Method::GET
|
||||
&& has_single_segment_after_prefix(normalized_path, "/api/wallet/recharge/")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"wallet_legacy",
|
||||
"wallet",
|
||||
"recharge_detail",
|
||||
"user:wallet",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path.starts_with("/api/wallet/refunds/") {
|
||||
} else if method == http::Method::GET
|
||||
&& has_single_segment_after_prefix(normalized_path, "/api/wallet/refunds/")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"wallet_legacy",
|
||||
"wallet",
|
||||
"refund_detail",
|
||||
"user:wallet",
|
||||
false,
|
||||
@@ -339,16 +368,17 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"wallet_legacy",
|
||||
"wallet",
|
||||
route_kind,
|
||||
"user:wallet",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path.starts_with("/api/payment/callback/")
|
||||
} else if method == http::Method::POST
|
||||
&& has_single_segment_after_prefix(normalized_path, "/api/payment/callback/")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"payment_callback_legacy",
|
||||
"payment_callback",
|
||||
"callback",
|
||||
"public:payment",
|
||||
false,
|
||||
@@ -387,7 +417,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
@@ -400,7 +430,7 @@ pub(super) fn classify_public_support_route(
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
"management_tokens_list",
|
||||
"user:self",
|
||||
false,
|
||||
@@ -419,7 +449,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
@@ -432,7 +462,7 @@ pub(super) fn classify_public_support_route(
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
"management_tokens_create",
|
||||
"user:self",
|
||||
false,
|
||||
@@ -443,7 +473,7 @@ pub(super) fn classify_public_support_route(
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
"management_token_regenerate",
|
||||
"user:self",
|
||||
false,
|
||||
@@ -451,7 +481,7 @@ pub(super) fn classify_public_support_route(
|
||||
} else if method == http::Method::PATCH && normalized_path == "/api/users/me/password" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
"password",
|
||||
"user:self",
|
||||
false,
|
||||
@@ -462,7 +492,7 @@ pub(super) fn classify_public_support_route(
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
"management_token_toggle",
|
||||
"user:self",
|
||||
false,
|
||||
@@ -470,13 +500,13 @@ pub(super) fn classify_public_support_route(
|
||||
} else if method == http::Method::DELETE && normalized_path == "/api/users/me/sessions/others" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
"sessions_others_delete",
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if matches!(method, &http::Method::PATCH | &http::Method::DELETE)
|
||||
&& normalized_path.starts_with("/api/users/me/sessions/")
|
||||
&& has_single_segment_after_prefix(normalized_path, "/api/users/me/sessions/")
|
||||
{
|
||||
let route_kind = if method == http::Method::PATCH {
|
||||
"session_update"
|
||||
@@ -485,7 +515,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
@@ -500,14 +530,21 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& (normalized_path.ends_with("/providers") || normalized_path.ends_with("/capabilities"))
|
||||
&& normalized_path.starts_with("/api/users/me/api-keys/")
|
||||
&& (has_single_nested_suffix_after_prefix(
|
||||
normalized_path,
|
||||
"/api/users/me/api-keys/",
|
||||
"providers",
|
||||
) || has_single_nested_suffix_after_prefix(
|
||||
normalized_path,
|
||||
"/api/users/me/api-keys/",
|
||||
"capabilities",
|
||||
))
|
||||
{
|
||||
let route_kind = if normalized_path.ends_with("/providers") {
|
||||
"api_key_providers_update"
|
||||
@@ -516,7 +553,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
@@ -524,7 +561,7 @@ pub(super) fn classify_public_support_route(
|
||||
} else if matches!(
|
||||
method,
|
||||
&http::Method::GET | &http::Method::PUT | &http::Method::PATCH | &http::Method::DELETE
|
||||
) && normalized_path.starts_with("/api/users/me/api-keys/")
|
||||
) && has_single_segment_after_prefix(normalized_path, "/api/users/me/api-keys/")
|
||||
{
|
||||
let route_kind = match *method {
|
||||
http::Method::GET => "api_key_detail",
|
||||
@@ -535,7 +572,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
@@ -543,7 +580,7 @@ pub(super) fn classify_public_support_route(
|
||||
} else if matches!(
|
||||
method,
|
||||
&http::Method::GET | &http::Method::PUT | &http::Method::DELETE
|
||||
) && normalized_path.starts_with("/api/me/management-tokens/")
|
||||
) && has_single_segment_after_prefix(normalized_path, "/api/me/management-tokens/")
|
||||
{
|
||||
let route_kind = match *method {
|
||||
http::Method::GET => "management_token_detail",
|
||||
@@ -553,7 +590,7 @@ pub(super) fn classify_public_support_route(
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"users_me",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_adaptive_keys_list_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_api_keys_list_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_billing_presets_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_health_api_formats_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_health_summary_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_audit_logs_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_oauth_start_key_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_payments_list_orders_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_pool_overview_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_architectures_list_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_query_models_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_strategy_list_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_create_provider_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_proxy_nodes_list_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_security_blacklist_add_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_provider_quota_usage_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_stats_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_users_list_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_video_tasks_list_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_list_as_admin_proxy_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_claude_count_tokens_as_non_execution_runtime_public_route() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_internal_tunnel_heartbeat_as_internal_proxy_route() {
|
||||
@@ -29,7 +31,7 @@ fn classifies_internal_gateway_resolve_as_internal_proxy_route() {
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("internal_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("gateway_legacy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("internal_gateway"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("resolve"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, GatewayPublicRequestContext};
|
||||
|
||||
fn headers(items: &[(&str, &str)]) -> http::HeaderMap {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use http::Uri;
|
||||
|
||||
use super::{classify_control_route, headers};
|
||||
|
||||
#[test]
|
||||
fn classifies_models_list_as_public_support_route() {
|
||||
@@ -174,14 +176,14 @@ fn classifies_public_announcement_detail_as_public_support_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_dashboard_stats_as_public_support_legacy_route() {
|
||||
fn classifies_dashboard_stats_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/dashboard/stats".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("dashboard_legacy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("dashboard"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -191,7 +193,7 @@ fn classifies_dashboard_stats_as_public_support_legacy_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_user_monitoring_audit_logs_as_public_support_legacy_route() {
|
||||
fn classifies_user_monitoring_audit_logs_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/monitoring/my-audit-logs"
|
||||
.parse()
|
||||
@@ -200,10 +202,7 @@ fn classifies_user_monitoring_audit_logs_as_public_support_legacy_route() {
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("monitoring_user_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring_user"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("audit_logs"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -213,7 +212,7 @@ fn classifies_user_monitoring_audit_logs_as_public_support_legacy_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_announcement_unread_count_as_public_support_legacy_route() {
|
||||
fn classifies_announcement_unread_count_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/users/me/unread-count"
|
||||
.parse()
|
||||
@@ -222,10 +221,7 @@ fn classifies_announcement_unread_count_as_public_support_legacy_route() {
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcement_user_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("unread_count"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -235,7 +231,7 @@ fn classifies_announcement_unread_count_as_public_support_legacy_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_announcement_read_status_as_public_support_legacy_route() {
|
||||
fn classifies_announcement_read_status_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/announcement-1/read-status"
|
||||
.parse()
|
||||
@@ -244,10 +240,7 @@ fn classifies_announcement_read_status_as_public_support_legacy_route() {
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcement_user_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("read_status"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -257,7 +250,7 @@ fn classifies_announcement_read_status_as_public_support_legacy_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_announcement_read_all_as_public_support_legacy_route() {
|
||||
fn classifies_announcement_read_all_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/read-all"
|
||||
.parse()
|
||||
@@ -266,10 +259,7 @@ fn classifies_announcement_read_all_as_public_support_legacy_route() {
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcement_user_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("read_all"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -279,7 +269,7 @@ fn classifies_announcement_read_all_as_public_support_legacy_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_wallet_legacy_routes_as_public_support_legacy_route() {
|
||||
fn classifies_wallet_routes_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
for (method, uri, route_kind) in [
|
||||
(http::Method::GET, "/api/wallet/balance", "balance"),
|
||||
@@ -322,7 +312,7 @@ fn classifies_wallet_legacy_routes_as_public_support_legacy_route() {
|
||||
classify_control_route(&method, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallet_legacy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallet"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -333,7 +323,7 @@ fn classifies_wallet_legacy_routes_as_public_support_legacy_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_users_me_legacy_routes_as_public_support_legacy_route() {
|
||||
fn classifies_users_me_routes_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
for (method, uri, route_kind) in [
|
||||
(http::Method::GET, "/api/users/me", "detail"),
|
||||
@@ -436,7 +426,7 @@ fn classifies_users_me_legacy_routes_as_public_support_legacy_route() {
|
||||
classify_control_route(&method, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("users_me_legacy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("users_me"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -447,7 +437,7 @@ fn classifies_users_me_legacy_routes_as_public_support_legacy_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_payment_callback_as_public_support_legacy_route() {
|
||||
fn classifies_payment_callback_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/payment/callback/alipay"
|
||||
.parse()
|
||||
@@ -456,10 +446,7 @@ fn classifies_payment_callback_as_public_support_legacy_route() {
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("payment_callback_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_family.as_deref(), Some("payment_callback"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("callback"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -620,7 +607,7 @@ fn classifies_auth_settings_as_public_support_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_auth_legacy_routes_as_public_support_route() {
|
||||
fn classifies_auth_routes_as_public_support_route() {
|
||||
for (method, path, route_kind) in [
|
||||
(http::Method::POST, "/api/auth/login", "login"),
|
||||
(http::Method::POST, "/api/auth/refresh", "refresh"),
|
||||
@@ -645,7 +632,7 @@ fn classifies_auth_legacy_routes_as_public_support_route() {
|
||||
classify_control_route(&method, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("auth_legacy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("auth"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
@@ -656,71 +643,45 @@ fn classifies_auth_legacy_routes_as_public_support_route() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_oauth_public_providers_as_public_support_route() {
|
||||
fn does_not_classify_oauth_public_providers_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/oauth/providers".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
let decision = classify_control_route(&http::Method::GET, &uri, &headers);
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("oauth_public_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("providers"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
assert!(decision.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_oauth_public_authorize_as_public_support_route() {
|
||||
fn does_not_classify_oauth_public_authorize_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/oauth/linuxdo/authorize?client_device_id=device-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
let decision = classify_control_route(&http::Method::GET, &uri, &headers);
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("oauth_public_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("authorize"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
assert!(decision.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_oauth_user_bindable_providers_as_public_support_route() {
|
||||
fn does_not_classify_oauth_user_bindable_providers_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/user/oauth/bindable-providers"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
let decision = classify_control_route(&http::Method::GET, &uri, &headers);
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_user_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("bindable_providers"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
assert!(decision.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_oauth_user_bind_token_as_public_support_route() {
|
||||
fn does_not_classify_oauth_user_bind_token_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/user/oauth/linuxdo/bind-token"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers);
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_user_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("bind_token"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
assert!(decision.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -19,10 +19,10 @@ impl IntoResponse for GatewayError {
|
||||
fn into_response(self) -> Response<Body> {
|
||||
match self {
|
||||
Self::UpstreamUnavailable { trace_id, message } => {
|
||||
warn!(trace_id = %trace_id, error = %message, "gateway upstream unavailable");
|
||||
warn!(trace_id = %trace_id, error = %message, "gateway proxy unavailable");
|
||||
let body = Json(json!({
|
||||
"error": {
|
||||
"message": "gateway upstream unavailable",
|
||||
"message": "gateway proxy unavailable",
|
||||
"trace_id": trace_id,
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -251,7 +251,9 @@ pub(crate) fn append_execution_contract_fields_to_value(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::{
|
||||
append_execution_contract_fields_to_value, ConversionMode, ExecutionStrategy,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -32,7 +32,7 @@ pub(crate) async fn maybe_execute_via_execution_runtime_stream(
|
||||
#[cfg(test)]
|
||||
{
|
||||
if state
|
||||
.test_remote_execution_runtime_base_url()
|
||||
.execution_runtime_override_base_url()
|
||||
.unwrap_or_default()
|
||||
.is_empty()
|
||||
&& parts.method != http::Method::POST
|
||||
|
||||
@@ -98,7 +98,7 @@ pub(crate) async fn execute_execution_runtime_stream(
|
||||
#[cfg(test)]
|
||||
{
|
||||
let remote_execution_runtime_base_url = state
|
||||
.test_remote_execution_runtime_base_url()
|
||||
.execution_runtime_override_base_url()
|
||||
.unwrap_or_default();
|
||||
if remote_execution_runtime_base_url.trim().is_empty() {
|
||||
let execution = match DirectSyncExecutionRuntime::new()
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) async fn maybe_execute_via_execution_runtime_sync(
|
||||
#[cfg(test)]
|
||||
{
|
||||
if state
|
||||
.test_remote_execution_runtime_base_url()
|
||||
.execution_runtime_override_base_url()
|
||||
.unwrap_or_default()
|
||||
.is_empty()
|
||||
&& parts.method != http::Method::POST
|
||||
|
||||
@@ -101,7 +101,7 @@ pub(crate) async fn execute_execution_runtime_sync(
|
||||
#[cfg(test)]
|
||||
let result = {
|
||||
let remote_execution_runtime_base_url = state
|
||||
.test_remote_execution_runtime_base_url()
|
||||
.execution_runtime_override_base_url()
|
||||
.unwrap_or_default();
|
||||
if remote_execution_runtime_base_url.trim().is_empty() {
|
||||
match DirectSyncExecutionRuntime::new()
|
||||
|
||||
@@ -216,7 +216,7 @@ pub(crate) async fn execute_sync_plan(
|
||||
#[cfg(test)]
|
||||
{
|
||||
let remote_execution_runtime_base_url = state
|
||||
.test_remote_execution_runtime_base_url()
|
||||
.execution_runtime_override_base_url()
|
||||
.unwrap_or_default();
|
||||
if !remote_execution_runtime_base_url.trim().is_empty() {
|
||||
return execute_sync_plan_via_remote_execution_runtime(
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::sync::Mutex;
|
||||
|
||||
use aether_runtime::{MetricKind, MetricLabel, MetricSample};
|
||||
|
||||
use crate::gateway::constants::LEGACY_INTERNAL_GATEWAY_SUNSET_DATE;
|
||||
use crate::gateway::GatewayControlDecision;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -12,10 +11,7 @@ pub(crate) enum GatewayFallbackMetricKind {
|
||||
PlanFallback,
|
||||
ControlExecuteFallback,
|
||||
LocalExecutionRuntimeMiss,
|
||||
PublicProxyAfterExecutionRuntimeMiss,
|
||||
PublicProxyPassthrough,
|
||||
LegacyInternalBridge,
|
||||
PythonExecuteEmergency,
|
||||
RemoteExecuteEmergency,
|
||||
}
|
||||
|
||||
impl GatewayFallbackMetricKind {
|
||||
@@ -25,12 +21,7 @@ impl GatewayFallbackMetricKind {
|
||||
Self::PlanFallback => "plan_fallback_total",
|
||||
Self::ControlExecuteFallback => "control_execute_fallback_total",
|
||||
Self::LocalExecutionRuntimeMiss => "local_execution_runtime_miss_total",
|
||||
Self::PublicProxyAfterExecutionRuntimeMiss => {
|
||||
"public_proxy_after_execution_runtime_miss_total"
|
||||
}
|
||||
Self::PublicProxyPassthrough => "public_proxy_passthrough_total",
|
||||
Self::LegacyInternalBridge => "legacy_internal_bridge_total",
|
||||
Self::PythonExecuteEmergency => "python_execute_emergency_total",
|
||||
Self::RemoteExecuteEmergency => "remote_execute_emergency_total",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,25 +35,10 @@ impl GatewayFallbackMetricKind {
|
||||
"Number of requests that fell back to Python control execution."
|
||||
}
|
||||
Self::LocalExecutionRuntimeMiss => {
|
||||
"Number of requests that were terminated locally after execution runtime miss because Python fallback was removed."
|
||||
"Number of requests that were terminated locally after execution runtime miss because no proxy fallback exists."
|
||||
}
|
||||
Self::PublicProxyAfterExecutionRuntimeMiss => {
|
||||
"Number of requests that fell through to Python public proxy after execution runtime miss."
|
||||
}
|
||||
Self::PublicProxyPassthrough => {
|
||||
"Number of requests that were proxied to Python public routes without local execution."
|
||||
}
|
||||
Self::LegacyInternalBridge => {
|
||||
Box::leak(
|
||||
format!(
|
||||
"Number of requests that still used the legacy internal gateway bridge scheduled to sunset on {}.",
|
||||
LEGACY_INTERNAL_GATEWAY_SUNSET_DATE
|
||||
)
|
||||
.into_boxed_str(),
|
||||
)
|
||||
}
|
||||
Self::PythonExecuteEmergency => {
|
||||
"Number of requests that used Python emergency execution fallback."
|
||||
Self::RemoteExecuteEmergency => {
|
||||
"Number of requests that used remote emergency execution fallback."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,8 +51,7 @@ pub(crate) enum GatewayFallbackReason {
|
||||
SchedulerDecisionUnsupported,
|
||||
ExecutionRuntimeMiss,
|
||||
ProxyPassthrough,
|
||||
PythonFallbackRemoved,
|
||||
LegacyInternalGateway,
|
||||
LocalExecutionPathRequired,
|
||||
ControlExecuteEmergency,
|
||||
ExecutionRuntimeMissing,
|
||||
}
|
||||
@@ -89,8 +64,7 @@ impl GatewayFallbackReason {
|
||||
Self::SchedulerDecisionUnsupported => "scheduler_decision_unsupported",
|
||||
Self::ExecutionRuntimeMiss => "execution_runtime_miss",
|
||||
Self::ProxyPassthrough => "proxy_passthrough",
|
||||
Self::PythonFallbackRemoved => "python_fallback_removed",
|
||||
Self::LegacyInternalGateway => "legacy_internal_gateway",
|
||||
Self::LocalExecutionPathRequired => "local_execution_path_required",
|
||||
Self::ControlExecuteEmergency => "control_execute_emergency",
|
||||
Self::ExecutionRuntimeMissing => "execution_runtime_missing",
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
ai_pipeline, async_task, audit, auth, control, error, execution_runtime, fallback_metrics,
|
||||
gateway_cache, gateway_data, handlers, hooks, intent, maintenance, middleware, model_fetch,
|
||||
rate_limit, router, state, tunnel, usage, wallet_runtime,
|
||||
};
|
||||
|
||||
pub(crate) use aether_data::repository::proxy_nodes::{
|
||||
ProxyNodeHeartbeatMutation, ProxyNodeTunnelStatusMutation, StoredProxyNode,
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
use super::*;
|
||||
use axum::routing::any;
|
||||
use axum::Router;
|
||||
|
||||
pub fn build_router(upstream_base_url: impl Into<String>) -> Result<Router, reqwest::Error> {
|
||||
Ok(build_router_with_state(AppState::new(upstream_base_url)?))
|
||||
use super::{
|
||||
api, middleware, prometheus_response, proxy_request, AppState, ConcurrencyError,
|
||||
DistributedConcurrencyError,
|
||||
};
|
||||
|
||||
pub fn build_router() -> Result<Router, reqwest::Error> {
|
||||
Ok(build_router_with_state(AppState::new()?))
|
||||
}
|
||||
|
||||
pub fn build_router_with_state(state: AppState) -> Router {
|
||||
@@ -38,12 +44,9 @@ pub(crate) enum RequestAdmissionError {
|
||||
Distributed(DistributedConcurrencyError),
|
||||
}
|
||||
|
||||
pub async fn serve_tcp(
|
||||
bind: &str,
|
||||
upstream_base_url: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn serve_tcp(bind: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = tokio::net::TcpListener::bind(bind).await?;
|
||||
let router = build_router(upstream_base_url.to_string())?;
|
||||
let router = build_router()?;
|
||||
axum::serve(
|
||||
listener,
|
||||
router.into_make_service_with_connect_info::<std::net::SocketAddr>(),
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use aether_runtime::{ConcurrencyGate, DistributedConcurrencyGate};
|
||||
|
||||
use super::async_task::{VideoTaskPollerConfig, VideoTaskService};
|
||||
use super::error::GatewayError;
|
||||
use super::fallback_metrics;
|
||||
use super::gateway_cache::{
|
||||
AuthApiKeyLastUsedCache, AuthContextCache, DirectPlanBypassCache, SchedulerAffinityCache,
|
||||
};
|
||||
use super::gateway_data::GatewayDataState;
|
||||
use super::rate_limit::FrontdoorUserRpmLimiter;
|
||||
use super::{provider_transport, usage};
|
||||
#[path = "state/catalog.rs"]
|
||||
mod catalog;
|
||||
#[path = "state/core.rs"]
|
||||
@@ -338,9 +353,8 @@ impl FrontdoorCorsConfig {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppState {
|
||||
pub(in crate::gateway) upstream_base_url: String,
|
||||
#[cfg(test)]
|
||||
pub(in crate::gateway) test_remote_execution_runtime_base_url: Option<String>,
|
||||
pub(in crate::gateway) execution_runtime_override_base_url: Option<String>,
|
||||
pub(in crate::gateway) data: Arc<GatewayDataState>,
|
||||
pub(in crate::gateway) usage_runtime: Arc<usage::UsageRuntime>,
|
||||
pub(in crate::gateway) video_tasks: Arc<VideoTaskService>,
|
||||
@@ -429,10 +443,6 @@ pub struct AppState {
|
||||
Arc<StdMutex<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
pub(super) fn normalize_upstream_base_url(upstream_base_url: String) -> String {
|
||||
upstream_base_url.trim_end_matches('/').to_string()
|
||||
}
|
||||
|
||||
pub(super) fn provider_transport_snapshot_looks_refreshed(
|
||||
current: &provider_transport::GatewayProviderTransportSnapshot,
|
||||
refreshed: &provider_transport::GatewayProviderTransportSnapshot,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{AppState, GatewayError, LocalMutationOutcome, LocalProviderDeleteTaskState};
|
||||
|
||||
impl AppState {
|
||||
pub fn has_provider_catalog_data_reader(&self) -> bool {
|
||||
|
||||
@@ -1,6 +1,44 @@
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use aether_data::repository::proxy_nodes::{
|
||||
ProxyNodeHeartbeatMutation, ProxyNodeTunnelStatusMutation, StoredProxyNode,
|
||||
StoredProxyNodeEvent,
|
||||
};
|
||||
use aether_http::{build_http_client, HttpClientConfig};
|
||||
use aether_runtime::{
|
||||
service_up_sample, AdmissionPermit, ConcurrencyGate, ConcurrencySnapshot,
|
||||
DistributedConcurrencyError, DistributedConcurrencyGate, DistributedConcurrencySnapshot,
|
||||
MetricKind, MetricLabel, MetricSample,
|
||||
};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::{AppState, FrontdoorCorsConfig, LocalExecutionRuntimeMissDiagnostic};
|
||||
|
||||
use super::super::async_task::{
|
||||
spawn_video_task_poller, VideoTaskPollerConfig, VideoTaskService, VideoTaskTruthSourceMode,
|
||||
};
|
||||
use super::super::fallback_metrics;
|
||||
use super::super::fallback_metrics::{GatewayFallbackMetricKind, GatewayFallbackReason};
|
||||
use super::super::gateway_cache::{
|
||||
AuthApiKeyLastUsedCache, AuthContextCache, DirectPlanBypassCache, SchedulerAffinityCache,
|
||||
};
|
||||
use super::super::gateway_data::{GatewayDataConfig, GatewayDataState};
|
||||
use super::super::model_fetch::spawn_model_fetch_worker;
|
||||
use super::super::rate_limit::{FrontdoorUserRpmConfig, FrontdoorUserRpmLimiter};
|
||||
use super::super::router::RequestAdmissionError;
|
||||
use super::super::{control::GatewayControlDecision, error::GatewayError};
|
||||
use super::super::{provider_transport, scheduler, usage};
|
||||
|
||||
use crate::gateway::maintenance::spawn_audit_cleanup_worker;
|
||||
use crate::gateway::maintenance::spawn_db_maintenance_worker;
|
||||
use crate::gateway::maintenance::spawn_gemini_file_mapping_cleanup_worker;
|
||||
use crate::gateway::maintenance::spawn_pending_cleanup_worker;
|
||||
use crate::gateway::maintenance::spawn_pool_monitor_worker;
|
||||
use crate::gateway::maintenance::spawn_provider_checkin_worker;
|
||||
use crate::gateway::maintenance::spawn_request_candidate_cleanup_worker;
|
||||
use crate::gateway::maintenance::spawn_stats_aggregation_worker;
|
||||
use crate::gateway::maintenance::spawn_stats_hourly_aggregation_worker;
|
||||
use crate::gateway::maintenance::spawn_usage_cleanup_worker;
|
||||
@@ -17,22 +55,26 @@ impl AppState {
|
||||
self.tunnel.request_close_all_proxies()
|
||||
}
|
||||
|
||||
pub fn new(upstream_base_url: impl Into<String>) -> Result<Self, reqwest::Error> {
|
||||
Self::build(upstream_base_url, None)
|
||||
pub fn new() -> Result<Self, reqwest::Error> {
|
||||
Self::build(None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new_with_test_remote_execution_runtime(
|
||||
upstream_base_url: impl Into<String>,
|
||||
test_remote_execution_runtime_base_url: Option<String>,
|
||||
) -> Result<Self, reqwest::Error> {
|
||||
Self::build(upstream_base_url, test_remote_execution_runtime_base_url)
|
||||
pub(crate) fn with_execution_runtime_override_base_url(
|
||||
mut self,
|
||||
execution_runtime_override_base_url: impl Into<String>,
|
||||
) -> Self {
|
||||
self.execution_runtime_override_base_url = Some(
|
||||
execution_runtime_override_base_url
|
||||
.into()
|
||||
.trim_end_matches('/')
|
||||
.to_string(),
|
||||
)
|
||||
.filter(|value| !value.is_empty());
|
||||
self
|
||||
}
|
||||
|
||||
fn build(
|
||||
upstream_base_url: impl Into<String>,
|
||||
test_remote_execution_runtime_base_url: Option<String>,
|
||||
) -> Result<Self, reqwest::Error> {
|
||||
fn build(execution_runtime_override_base_url: Option<String>) -> Result<Self, reqwest::Error> {
|
||||
let data = Arc::new(GatewayDataState::disabled());
|
||||
let client = build_http_client(&HttpClientConfig {
|
||||
connect_timeout_ms: Some(10_000),
|
||||
@@ -41,10 +83,9 @@ impl AppState {
|
||||
..HttpClientConfig::default()
|
||||
})?;
|
||||
Ok(Self {
|
||||
upstream_base_url: normalize_upstream_base_url(upstream_base_url.into()),
|
||||
#[cfg(test)]
|
||||
test_remote_execution_runtime_base_url: test_remote_execution_runtime_base_url
|
||||
.map(normalize_upstream_base_url)
|
||||
execution_runtime_override_base_url: execution_runtime_override_base_url
|
||||
.map(|value| value.trim_end_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty()),
|
||||
data: Arc::clone(&data),
|
||||
usage_runtime: Arc::new(usage::UsageRuntime::disabled()),
|
||||
@@ -121,8 +162,8 @@ impl AppState {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn test_remote_execution_runtime_base_url(&self) -> Option<&str> {
|
||||
self.test_remote_execution_runtime_base_url.as_deref()
|
||||
pub(crate) fn execution_runtime_override_base_url(&self) -> Option<&str> {
|
||||
self.execution_runtime_override_base_url.as_deref()
|
||||
}
|
||||
|
||||
pub fn with_data_config(
|
||||
@@ -160,6 +201,14 @@ impl AppState {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub async fn run_postgres_migrations(&self) -> Result<bool, sqlx::migrate::MigrateError> {
|
||||
let Some(pool) = self.postgres_pool() else {
|
||||
return Ok(false);
|
||||
};
|
||||
aether_data::migrate::run_migrations(&pool).await?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn with_video_task_poller_config(mut self, interval: Duration, batch_size: usize) -> Self {
|
||||
self.video_task_poller = Some(VideoTaskPollerConfig {
|
||||
interval,
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
provider_transport_snapshot_looks_refreshed, AppState, CachedProviderTransportSnapshot,
|
||||
GatewayError, ProviderTransportSnapshotCacheKey, PROVIDER_TRANSPORT_SNAPSHOT_CACHE_MAX_ENTRIES,
|
||||
PROVIDER_TRANSPORT_SNAPSHOT_CACHE_TTL,
|
||||
};
|
||||
|
||||
use super::super::provider_transport;
|
||||
use std::time::Duration;
|
||||
|
||||
use aether_crypto::encrypt_python_fernet_plaintext;
|
||||
|
||||
impl AppState {
|
||||
pub(in crate::gateway) fn clear_provider_transport_snapshot_cache(&self) {
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
use super::*;
|
||||
use super::super::error::GatewayError;
|
||||
use super::super::{scheduler, usage};
|
||||
use super::{
|
||||
AdminBillingCollectorRecord, AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult,
|
||||
AdminBillingRuleRecord, AdminBillingRuleWriteInput, AdminPaymentCallbackRecord,
|
||||
AdminSecurityBlacklistEntry, AdminWalletMutationOutcome, AdminWalletPaymentOrderRecord,
|
||||
AdminWalletRefundRecord, AdminWalletTransactionRecord, AppState, LocalMutationOutcome,
|
||||
AUTH_API_KEY_LAST_USED_MAX_ENTRIES, AUTH_API_KEY_LAST_USED_TTL,
|
||||
};
|
||||
|
||||
use sqlx::Row;
|
||||
|
||||
#[path = "runtime/admin_finance_queries.rs"]
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
use super::*;
|
||||
use sqlx::Row;
|
||||
|
||||
use crate::gateway::state::AdminPaymentCallbackRecord;
|
||||
use crate::gateway::{
|
||||
AdminWalletMutationOutcome, AdminWalletPaymentOrderRecord, AdminWalletRefundRecord,
|
||||
AdminWalletTransactionRecord, AppState, GatewayError,
|
||||
};
|
||||
|
||||
use super::admin_wallet_payment_order_from_row;
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn list_admin_payment_orders(
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
use super::*;
|
||||
use sqlx::Row;
|
||||
|
||||
use crate::gateway::{
|
||||
AdminWalletMutationOutcome, AdminWalletPaymentOrderRecord, AppState, GatewayError,
|
||||
};
|
||||
|
||||
use super::{
|
||||
admin_payment_gateway_response_map, admin_wallet_payment_order_from_row,
|
||||
admin_wallet_snapshot_from_row,
|
||||
};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn admin_expire_payment_order(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::gateway::state::AdminSecurityBlacklistEntry;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn add_admin_security_blacklist(
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use super::*;
|
||||
use sqlx::Row;
|
||||
|
||||
use crate::gateway::{
|
||||
AdminWalletPaymentOrderRecord, AdminWalletTransactionRecord, AppState, GatewayError,
|
||||
};
|
||||
|
||||
use super::{admin_wallet_build_order_no, admin_wallet_snapshot_from_row};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn admin_adjust_wallet_balance(
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use super::*;
|
||||
use sqlx::Row;
|
||||
|
||||
use super::{
|
||||
admin_wallet_refund_from_row, admin_wallet_snapshot_from_row, AdminWalletMutationOutcome,
|
||||
AdminWalletRefundRecord, AdminWalletTransactionRecord, AppState, GatewayError,
|
||||
};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn admin_process_wallet_refund(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn list_announcements(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn list_auth_api_key_export_records_by_user_ids(
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use crate::gateway::{scheduler, usage, AppState, GatewayError};
|
||||
|
||||
use super::super::{AUTH_API_KEY_LAST_USED_MAX_ENTRIES, AUTH_API_KEY_LAST_USED_TTL};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn read_request_candidate_trace(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn find_user_session(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn find_user_auth_by_id(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn read_user_model_capability_settings(
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
admin_billing_collector_from_row, admin_billing_rule_from_row, AdminBillingCollectorRecord,
|
||||
AdminBillingCollectorWriteInput, AdminBillingPresetApplyResult, AdminBillingRuleRecord,
|
||||
AdminBillingRuleWriteInput, AppState, GatewayError, LocalMutationOutcome,
|
||||
};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn admin_billing_enabled_default_value_exists(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn list_minimal_candidate_selection_rows_for_api_format(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn upsert_gemini_file_mapping(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn record_shadow_result_sample(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn read_request_candidates_by_request_id(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn read_user_preferences(
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use super::*;
|
||||
use sqlx::Row;
|
||||
|
||||
use super::{
|
||||
AdminBillingCollectorRecord, AdminBillingRuleRecord, AdminWalletPaymentOrderRecord,
|
||||
AdminWalletRefundRecord, GatewayError,
|
||||
};
|
||||
|
||||
pub(super) fn admin_wallet_build_order_no(now: chrono::DateTime<chrono::Utc>) -> String {
|
||||
format!(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn find_wallet(
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex as StdMutex};
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use super::{AppState, GatewayDataState};
|
||||
use crate::gateway::{provider_transport, usage};
|
||||
|
||||
#[cfg(test)]
|
||||
impl AppState {
|
||||
pub(crate) fn with_data_state_for_tests(mut self, data_state: GatewayDataState) -> Self {
|
||||
@@ -269,6 +274,16 @@ impl AppState {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn without_auth_user_store_for_tests(mut self) -> Self {
|
||||
self.auth_user_store = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn without_auth_user_model_capability_store_for_tests(mut self) -> Self {
|
||||
self.auth_user_model_capability_store = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_auth_wallets_for_tests<I>(mut self, wallets: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = aether_data::repository::wallet::StoredWalletSnapshot>,
|
||||
@@ -303,7 +318,7 @@ impl AppState {
|
||||
|
||||
pub(crate) fn with_admin_payment_callbacks_for_tests<I>(mut self, callbacks: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = AdminPaymentCallbackRecord>,
|
||||
I: IntoIterator<Item = crate::gateway::state::AdminPaymentCallbackRecord>,
|
||||
{
|
||||
let store = self
|
||||
.admin_payment_callback_store
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{AppState, GatewayError};
|
||||
|
||||
use super::super::async_task;
|
||||
use super::super::async_task::video as video_tasks;
|
||||
|
||||
impl AppState {
|
||||
pub(crate) async fn read_data_backed_video_task_response(
|
||||
@@ -22,6 +25,18 @@ impl AppState {
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn find_video_task_by_short_id(
|
||||
&self,
|
||||
short_id: &str,
|
||||
) -> Result<Option<aether_data::repository::video_tasks::StoredVideoTask>, GatewayError> {
|
||||
self.data
|
||||
.find_video_task(
|
||||
aether_data::repository::video_tasks::VideoTaskLookupKey::ShortId(short_id),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| GatewayError::Internal(err.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) async fn upsert_video_task_snapshot(
|
||||
&self,
|
||||
snapshot: &video_tasks::LocalVideoTaskSnapshot,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
any, build_router, build_router_with_execution_runtime_override, json, start_server, Arc,
|
||||
Body, Bytes, HeaderName, HeaderValue, Infallible, Json, Mutex, Request, Response, Router,
|
||||
StatusCode,
|
||||
CONTROL_EXECUTED_HEADER, CONTROL_EXECUTE_FALLBACK_HEADER, DEPENDENCY_REASON_HEADER,
|
||||
EXECUTION_PATH_HEADER, EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS, TRACE_ID_HEADER,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execution_runtime_missing(
|
||||
@@ -20,7 +26,6 @@ async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execut
|
||||
"route_family": "openai",
|
||||
"route_kind": "chat",
|
||||
"auth_endpoint_signature": "openai:chat",
|
||||
"executor_candidate": true,
|
||||
"execution_runtime_candidate": true,
|
||||
"auth_context": {
|
||||
"user_id": "user-sync-123",
|
||||
@@ -84,7 +89,7 @@ async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execut
|
||||
);
|
||||
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let gateway = build_router(upstream_url.clone()).expect("gateway should build");
|
||||
let gateway = build_router().expect("gateway should build");
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
@@ -103,12 +108,12 @@ async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execut
|
||||
.get(EXECUTION_PATH_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(ToOwned::to_owned);
|
||||
let python_dependency_reason = response
|
||||
let dependency_reason = response
|
||||
.headers()
|
||||
.get(PYTHON_DEPENDENCY_REASON_HEADER)
|
||||
.get(DEPENDENCY_REASON_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(ToOwned::to_owned);
|
||||
assert_eq!(python_dependency_reason.as_deref(), None);
|
||||
assert_eq!(dependency_reason.as_deref(), None);
|
||||
let payload: serde_json::Value = response.json().await.expect("body should parse");
|
||||
assert_eq!(
|
||||
execution_path.as_deref(),
|
||||
@@ -116,7 +121,7 @@ async fn gateway_locally_denies_sync_ai_control_execute_when_opted_in_and_execut
|
||||
);
|
||||
assert_eq!(
|
||||
payload["error"]["message"],
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed"
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path"
|
||||
);
|
||||
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
|
||||
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
|
||||
@@ -152,7 +157,6 @@ async fn gateway_locally_denies_stream_ai_control_execute_when_opted_in_and_exec
|
||||
"route_family": "openai",
|
||||
"route_kind": "chat",
|
||||
"auth_endpoint_signature": "openai:chat",
|
||||
"executor_candidate": true,
|
||||
"execution_runtime_candidate": true,
|
||||
"auth_context": {
|
||||
"user_id": "user-stream-123",
|
||||
@@ -220,7 +224,7 @@ async fn gateway_locally_denies_stream_ai_control_execute_when_opted_in_and_exec
|
||||
);
|
||||
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let gateway = build_router(upstream_url.clone()).expect("gateway should build");
|
||||
let gateway = build_router().expect("gateway should build");
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
@@ -239,12 +243,12 @@ async fn gateway_locally_denies_stream_ai_control_execute_when_opted_in_and_exec
|
||||
.get(EXECUTION_PATH_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(ToOwned::to_owned);
|
||||
let python_dependency_reason = response
|
||||
let dependency_reason = response
|
||||
.headers()
|
||||
.get(PYTHON_DEPENDENCY_REASON_HEADER)
|
||||
.get(DEPENDENCY_REASON_HEADER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(ToOwned::to_owned);
|
||||
assert_eq!(python_dependency_reason.as_deref(), None);
|
||||
assert_eq!(dependency_reason.as_deref(), None);
|
||||
let payload: serde_json::Value = response.json().await.expect("body should parse");
|
||||
assert_eq!(
|
||||
execution_path.as_deref(),
|
||||
@@ -252,7 +256,7 @@ async fn gateway_locally_denies_stream_ai_control_execute_when_opted_in_and_exec
|
||||
);
|
||||
assert_eq!(
|
||||
payload["error"]["message"],
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed"
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path"
|
||||
);
|
||||
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
|
||||
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
|
||||
@@ -288,7 +292,6 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
|
||||
"route_family": "openai",
|
||||
"route_kind": "chat",
|
||||
"auth_endpoint_signature": "openai:chat",
|
||||
"executor_candidate": true,
|
||||
"execution_runtime_candidate": true,
|
||||
"public_path": "/v1/chat/completions"
|
||||
}))
|
||||
@@ -361,10 +364,7 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
|
||||
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway = build_router_with_test_remote_execution_runtime(
|
||||
upstream_url.clone(),
|
||||
execution_runtime_url,
|
||||
);
|
||||
let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
@@ -387,14 +387,14 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(PYTHON_DEPENDENCY_REASON_HEADER)
|
||||
.get(DEPENDENCY_REASON_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
None
|
||||
);
|
||||
let payload: serde_json::Value = response.json().await.expect("body should parse");
|
||||
assert_eq!(
|
||||
payload["error"]["message"],
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed"
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path"
|
||||
);
|
||||
assert_eq!(*plan_hits.lock().expect("mutex should lock"), 0);
|
||||
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
|
||||
@@ -425,7 +425,6 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
|
||||
"route_family": "openai",
|
||||
"route_kind": "chat",
|
||||
"auth_endpoint_signature": "openai:chat",
|
||||
"executor_candidate": true,
|
||||
"execution_runtime_candidate": true,
|
||||
"public_path": "/v1/chat/completions"
|
||||
}))
|
||||
@@ -498,10 +497,7 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
|
||||
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway = build_router_with_test_remote_execution_runtime(
|
||||
upstream_url.clone(),
|
||||
execution_runtime_url,
|
||||
);
|
||||
let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
@@ -524,14 +520,14 @@ async fn gateway_does_not_proxy_control_execute_over_http_when_opted_in_and_exec
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(PYTHON_DEPENDENCY_REASON_HEADER)
|
||||
.get(DEPENDENCY_REASON_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
None
|
||||
);
|
||||
let payload: serde_json::Value = response.json().await.expect("body should parse");
|
||||
assert_eq!(
|
||||
payload["error"]["message"],
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed"
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path"
|
||||
);
|
||||
assert_eq!(*plan_hits.lock().expect("mutex should lock"), 0);
|
||||
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
any, build_router, build_router_with_execution_runtime_override, json, start_server, Arc,
|
||||
Body, HeaderValue, Json, Mutex, Request, Response, Router, StatusCode,
|
||||
DEPENDENCY_REASON_HEADER, EXECUTION_PATH_HEADER, EXECUTION_PATH_LOCAL_EXECUTION_RUNTIME_MISS,
|
||||
LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER, TRACE_ID_HEADER,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_misses_without_control_execute_opt_in(
|
||||
@@ -20,7 +25,6 @@ async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_mis
|
||||
"route_family": "openai",
|
||||
"route_kind": "chat",
|
||||
"auth_endpoint_signature": "openai:chat",
|
||||
"executor_candidate": true,
|
||||
"execution_runtime_candidate": true,
|
||||
"public_path": "/v1/chat/completions"
|
||||
}))
|
||||
@@ -79,10 +83,7 @@ async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_mis
|
||||
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway = build_router_with_test_remote_execution_runtime(
|
||||
upstream_url.clone(),
|
||||
execution_runtime_url,
|
||||
);
|
||||
let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
@@ -106,7 +107,7 @@ async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_mis
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(PYTHON_DEPENDENCY_REASON_HEADER)
|
||||
.get(DEPENDENCY_REASON_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
None
|
||||
);
|
||||
@@ -121,7 +122,7 @@ async fn gateway_locally_denies_openai_chat_after_repeated_execution_runtime_mis
|
||||
assert_eq!(payload["error"]["type"], "http_error");
|
||||
assert_eq!(
|
||||
payload["error"]["message"],
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed"
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -156,7 +157,6 @@ async fn gateway_locally_denies_openai_chat_when_control_api_is_configured_witho
|
||||
"route_family": "openai",
|
||||
"route_kind": "chat",
|
||||
"auth_endpoint_signature": "openai:chat",
|
||||
"executor_candidate": true,
|
||||
"execution_runtime_candidate": true,
|
||||
"public_path": "/v1/chat/completions"
|
||||
}))
|
||||
@@ -211,7 +211,7 @@ async fn gateway_locally_denies_openai_chat_when_control_api_is_configured_witho
|
||||
);
|
||||
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let gateway = build_router(upstream_url.clone()).expect("gateway should build");
|
||||
let gateway = build_router().expect("gateway should build");
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
@@ -233,7 +233,7 @@ async fn gateway_locally_denies_openai_chat_when_control_api_is_configured_witho
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(PYTHON_DEPENDENCY_REASON_HEADER)
|
||||
.get(DEPENDENCY_REASON_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
None
|
||||
);
|
||||
@@ -248,7 +248,7 @@ async fn gateway_locally_denies_openai_chat_when_control_api_is_configured_witho
|
||||
assert_eq!(payload["error"]["type"], "http_error");
|
||||
assert_eq!(
|
||||
payload["error"]["message"],
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed"
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path"
|
||||
);
|
||||
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
|
||||
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
|
||||
@@ -283,7 +283,6 @@ async fn gateway_locally_denies_openai_chat_stream_after_execution_runtime_miss_
|
||||
"route_family": "openai",
|
||||
"route_kind": "chat",
|
||||
"auth_endpoint_signature": "openai:chat",
|
||||
"executor_candidate": true,
|
||||
"execution_runtime_candidate": true,
|
||||
"public_path": "/v1/chat/completions"
|
||||
}))
|
||||
@@ -323,10 +322,7 @@ async fn gateway_locally_denies_openai_chat_stream_after_execution_runtime_miss_
|
||||
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway = build_router_with_test_remote_execution_runtime(
|
||||
upstream_url.clone(),
|
||||
execution_runtime_url,
|
||||
);
|
||||
let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
@@ -348,7 +344,7 @@ async fn gateway_locally_denies_openai_chat_stream_after_execution_runtime_miss_
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(PYTHON_DEPENDENCY_REASON_HEADER)
|
||||
.get(DEPENDENCY_REASON_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
None
|
||||
);
|
||||
@@ -363,7 +359,7 @@ async fn gateway_locally_denies_openai_chat_stream_after_execution_runtime_miss_
|
||||
assert_eq!(payload["error"]["type"], "http_error");
|
||||
assert_eq!(
|
||||
payload["error"]["message"],
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path, and Python fallback has been removed"
|
||||
"OpenAI chat execution runtime miss did not match a Rust execution path"
|
||||
);
|
||||
assert_eq!(*execute_hits.lock().expect("mutex should lock"), 0);
|
||||
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
|
||||
@@ -428,7 +424,6 @@ async fn assert_ai_route_locally_denied_after_execution_runtime_miss_with_reques
|
||||
"route_family": route_family,
|
||||
"route_kind": route_kind,
|
||||
"auth_endpoint_signature": endpoint_signature,
|
||||
"executor_candidate": true,
|
||||
"execution_runtime_candidate": true,
|
||||
"public_path": route_path
|
||||
}))
|
||||
@@ -488,10 +483,7 @@ async fn assert_ai_route_locally_denied_after_execution_runtime_miss_with_reques
|
||||
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway = build_router_with_test_remote_execution_runtime(
|
||||
upstream_url.clone(),
|
||||
execution_runtime_url,
|
||||
);
|
||||
let gateway = build_router_with_execution_runtime_override(execution_runtime_url);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
let mut request =
|
||||
@@ -514,7 +506,7 @@ async fn assert_ai_route_locally_denied_after_execution_runtime_miss_with_reques
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(PYTHON_DEPENDENCY_REASON_HEADER)
|
||||
.get(DEPENDENCY_REASON_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
None
|
||||
);
|
||||
@@ -545,7 +537,7 @@ async fn gateway_locally_denies_openai_responses_after_execution_runtime_miss_wi
|
||||
"cli",
|
||||
"openai:cli",
|
||||
"{\"model\":\"gpt-5\",\"input\":\"hello\"}",
|
||||
"OpenAI responses execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"OpenAI responses execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -559,7 +551,7 @@ async fn gateway_locally_denies_claude_messages_after_execution_runtime_miss_wit
|
||||
"chat",
|
||||
"claude:chat",
|
||||
"{\"model\":\"claude-sonnet-4-5\",\"messages\":[]}",
|
||||
"Claude messages execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"Claude messages execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -573,7 +565,7 @@ async fn gateway_locally_denies_openai_responses_stream_after_execution_runtime_
|
||||
"cli",
|
||||
"openai:cli",
|
||||
"{\"model\":\"gpt-5\",\"input\":\"hello\",\"stream\":true}",
|
||||
"OpenAI responses execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"OpenAI responses execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -587,7 +579,7 @@ async fn gateway_locally_denies_claude_messages_stream_after_execution_runtime_m
|
||||
"chat",
|
||||
"claude:chat",
|
||||
"{\"model\":\"claude-sonnet-4-5\",\"messages\":[],\"stream\":true}",
|
||||
"Claude messages execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"Claude messages execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -601,7 +593,7 @@ async fn gateway_locally_denies_openai_compact_after_execution_runtime_miss_with
|
||||
"compact",
|
||||
"openai:compact",
|
||||
"{\"model\":\"gpt-5\",\"input\":\"hello\"}",
|
||||
"OpenAI compact execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"OpenAI compact execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -615,7 +607,7 @@ async fn gateway_locally_denies_openai_compact_stream_after_execution_runtime_mi
|
||||
"compact",
|
||||
"openai:compact",
|
||||
"{\"model\":\"gpt-5\",\"input\":\"hello\",\"stream\":true}",
|
||||
"OpenAI compact execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"OpenAI compact execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -629,7 +621,21 @@ async fn gateway_locally_denies_gemini_generate_after_execution_runtime_miss_wit
|
||||
"chat",
|
||||
"gemini:chat",
|
||||
"{\"contents\":[]}",
|
||||
"Gemini public execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"Gemini public execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_locally_denies_gemini_v1_generate_after_execution_runtime_miss_without_control_execute_opt_in(
|
||||
) {
|
||||
assert_ai_route_locally_denied_after_execution_runtime_miss(
|
||||
"/v1/models/gemini-2.5-pro:generateContent",
|
||||
"gemini",
|
||||
"chat",
|
||||
"gemini:chat",
|
||||
"{\"contents\":[]}",
|
||||
"Gemini public execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -643,7 +649,7 @@ async fn gateway_locally_denies_gemini_stream_after_execution_runtime_miss_witho
|
||||
"chat",
|
||||
"gemini:chat",
|
||||
"{\"contents\":[]}",
|
||||
"Gemini public execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"Gemini public execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -657,7 +663,7 @@ async fn gateway_locally_denies_openai_video_after_execution_runtime_miss_withou
|
||||
"video",
|
||||
"openai:video",
|
||||
"{\"model\":\"sora-2\"}",
|
||||
"OpenAI video execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"OpenAI video execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -671,7 +677,23 @@ async fn gateway_locally_denies_gemini_video_after_execution_runtime_miss_withou
|
||||
"video",
|
||||
"gemini:video",
|
||||
"{\"instances\":[]}",
|
||||
"Gemini public execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"Gemini public execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_locally_denies_gemini_files_root_after_execution_runtime_miss_without_control_execute_opt_in(
|
||||
) {
|
||||
assert_ai_route_locally_denied_after_execution_runtime_miss_with_request(
|
||||
reqwest::Method::GET,
|
||||
"/v1beta/files",
|
||||
"/v1beta/files?view=BASIC",
|
||||
"gemini",
|
||||
"files",
|
||||
"gemini:chat",
|
||||
None,
|
||||
"Gemini files execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -687,7 +709,7 @@ async fn gateway_locally_denies_gemini_files_download_after_execution_runtime_mi
|
||||
"files",
|
||||
"gemini:chat",
|
||||
None,
|
||||
"Gemini files execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"Gemini files execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -703,7 +725,7 @@ async fn gateway_locally_denies_gemini_files_upload_after_execution_runtime_miss
|
||||
"files",
|
||||
"gemini:chat",
|
||||
Some("{\"file\":{}}"),
|
||||
"Gemini files execution runtime miss did not match a Rust execution path, and Python fallback has been removed",
|
||||
"Gemini files execution runtime miss did not match a Rust execution path",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
any, build_router_with_state, build_state_with_execution_runtime_override, json,
|
||||
start_server, to_bytes, Arc, Body, Bytes, HeaderName, HeaderValue, Json, Mutex, Request,
|
||||
Response, Router, StatusCode, CONTROL_EXECUTED_HEADER, EXECUTION_PATH_HEADER,
|
||||
LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER, TRACE_ID_HEADER,
|
||||
};
|
||||
use crate::gateway::gateway_data::GatewayDataState;
|
||||
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
|
||||
use aether_data::repository::auth::{
|
||||
@@ -366,7 +371,7 @@ async fn gateway_executes_openai_chat_sync_upstream_stream_via_local_finalize_re
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_and_request_candidate_repository_for_tests(
|
||||
auth_repository,
|
||||
@@ -827,7 +832,7 @@ async fn gateway_executes_openai_chat_cross_format_upstream_stream_via_local_fin
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -1277,7 +1282,7 @@ async fn gateway_executes_openai_chat_cross_format_tool_use_upstream_stream_via_
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -1681,7 +1686,7 @@ async fn gateway_skips_openai_chat_antigravity_cross_format_sync_candidate_as_tr
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -2030,7 +2035,7 @@ async fn gateway_executes_openai_chat_cross_format_claude_upstream_sync_via_loca
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -2383,7 +2388,7 @@ async fn gateway_executes_openai_chat_cross_format_gemini_upstream_sync_via_loca
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
any, build_router_with_execution_runtime_override, build_router_with_state,
|
||||
build_state_with_execution_runtime_override, json, start_server, to_bytes, Arc, Body, Bytes,
|
||||
HeaderName, HeaderValue, Json, Mutex, Request, Response, Router, StatusCode,
|
||||
CONTROL_EXECUTED_HEADER, EXECUTION_PATH_EXECUTION_RUNTIME_SYNC, EXECUTION_PATH_HEADER,
|
||||
TRACE_ID_HEADER,
|
||||
};
|
||||
use crate::gateway::gateway_data::GatewayDataState;
|
||||
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
|
||||
use aether_data::repository::auth::{
|
||||
@@ -347,7 +353,7 @@ async fn gateway_executes_openai_compact_cross_format_upstream_stream_via_local_
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -802,7 +808,7 @@ async fn gateway_executes_openai_compact_openai_family_upstream_stream_via_local
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -1250,7 +1256,7 @@ async fn gateway_executes_openai_compact_openai_family_upstream_stream_via_local
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
any, build_router_with_execution_runtime_override, build_router_with_state,
|
||||
build_state_with_execution_runtime_override, json, start_server, to_bytes, Arc, Body, Bytes,
|
||||
HeaderName, HeaderValue, Json, Mutex, Request, Response, Router, StatusCode,
|
||||
CONTROL_EXECUTED_HEADER, EXECUTION_PATH_EXECUTION_RUNTIME_SYNC, EXECUTION_PATH_HEADER,
|
||||
TRACE_ID_HEADER,
|
||||
};
|
||||
use crate::gateway::gateway_data::GatewayDataState;
|
||||
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
|
||||
use aether_data::repository::auth::{
|
||||
@@ -346,7 +352,7 @@ async fn gateway_executes_openai_cli_cross_format_upstream_stream_via_local_fina
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -798,7 +804,7 @@ async fn gateway_executes_openai_cli_cross_format_function_call_upstream_stream_
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -1362,7 +1368,7 @@ async fn gateway_executes_openai_cli_antigravity_cross_format_upstream_stream_vi
|
||||
)],
|
||||
);
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
any, build_router_with_execution_runtime_override, build_router_with_state,
|
||||
build_state_with_execution_runtime_override, json, start_server, to_bytes, Arc, Body, Bytes,
|
||||
HeaderName, HeaderValue, Json, Mutex, Request, Response, Router, StatusCode,
|
||||
CONTROL_EXECUTED_HEADER, EXECUTION_PATH_EXECUTION_RUNTIME_SYNC, EXECUTION_PATH_HEADER,
|
||||
TRACE_ID_HEADER,
|
||||
};
|
||||
use crate::gateway::gateway_data::GatewayDataState;
|
||||
use aether_crypto::{encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY};
|
||||
use aether_data::repository::auth::{
|
||||
@@ -342,7 +348,7 @@ async fn gateway_executes_openai_cli_sync_upstream_stream_via_local_finalize_res
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
@@ -908,7 +914,7 @@ async fn gateway_executes_kiro_claude_cli_sync_upstream_stream_via_local_finaliz
|
||||
let (upstream_url, upstream_handle) = start_server(upstream).await;
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let gateway_state =
|
||||
build_state_with_test_remote_execution_runtime(upstream_url.clone(), execution_runtime_url.clone())
|
||||
build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_auth_candidate_selection_provider_catalog_request_candidates_and_usage_for_tests(
|
||||
auth_repository,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user