mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-06 09:27:46 +08:00
feat(codex): add provider outbound policy boundary
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use aether_provider_transport::CodexFingerprintConvergenceContext;
|
||||
use http::{request::Parts, HeaderMap};
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ai_serving::transport::ProviderOutboundRequestContext;
|
||||
use crate::client_session_affinity::codex_request_signals_from_request;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CodexFingerprintContextSlot(Arc<OnceLock<CodexFingerprintConvergenceContext>>);
|
||||
pub(crate) struct CodexFingerprintContextSlot(Arc<OnceLock<ProviderOutboundRequestContext>>);
|
||||
|
||||
impl Default for CodexFingerprintContextSlot {
|
||||
fn default() -> Self {
|
||||
@@ -18,11 +18,7 @@ impl Default for CodexFingerprintContextSlot {
|
||||
}
|
||||
|
||||
impl CodexFingerprintContextSlot {
|
||||
fn resolve(
|
||||
&self,
|
||||
headers: &HeaderMap,
|
||||
body_json: &Value,
|
||||
) -> CodexFingerprintConvergenceContext {
|
||||
fn resolve(&self, headers: &HeaderMap, body_json: &Value) -> ProviderOutboundRequestContext {
|
||||
self.0
|
||||
.get_or_init(|| {
|
||||
build_codex_fingerprint_context(headers, body_json, Uuid::now_v7().to_string())
|
||||
@@ -34,10 +30,10 @@ impl CodexFingerprintContextSlot {
|
||||
pub(crate) fn resolve_codex_fingerprint_context(
|
||||
parts: &Parts,
|
||||
body_json: &Value,
|
||||
) -> CodexFingerprintConvergenceContext {
|
||||
) -> ProviderOutboundRequestContext {
|
||||
if let Some(context) = parts
|
||||
.extensions
|
||||
.get::<CodexFingerprintConvergenceContext>()
|
||||
.get::<ProviderOutboundRequestContext>()
|
||||
.cloned()
|
||||
{
|
||||
return context;
|
||||
@@ -51,7 +47,7 @@ pub(crate) fn resolve_codex_fingerprint_context(
|
||||
pub(crate) fn install_codex_fingerprint_context_slot(parts: &mut Parts) {
|
||||
if parts
|
||||
.extensions
|
||||
.get::<CodexFingerprintConvergenceContext>()
|
||||
.get::<ProviderOutboundRequestContext>()
|
||||
.is_none()
|
||||
&& parts
|
||||
.extensions
|
||||
@@ -67,11 +63,11 @@ pub(crate) fn install_codex_fingerprint_context_slot(parts: &mut Parts) {
|
||||
pub(crate) fn ensure_codex_fingerprint_context(
|
||||
parts: &mut Parts,
|
||||
body_json: &Value,
|
||||
) -> CodexFingerprintConvergenceContext {
|
||||
) -> ProviderOutboundRequestContext {
|
||||
let context = resolve_codex_fingerprint_context(parts, body_json);
|
||||
if parts
|
||||
.extensions
|
||||
.get::<CodexFingerprintConvergenceContext>()
|
||||
.get::<ProviderOutboundRequestContext>()
|
||||
.is_none()
|
||||
{
|
||||
parts.extensions.remove::<CodexFingerprintContextSlot>();
|
||||
@@ -84,7 +80,7 @@ pub(crate) fn attach_codex_logical_turn_context(
|
||||
parts: &mut Parts,
|
||||
body_json: &Value,
|
||||
logical_turn_id: &str,
|
||||
) -> CodexFingerprintConvergenceContext {
|
||||
) -> ProviderOutboundRequestContext {
|
||||
let context =
|
||||
build_codex_fingerprint_context(&parts.headers, body_json, logical_turn_id.to_string());
|
||||
parts.extensions.remove::<CodexFingerprintContextSlot>();
|
||||
@@ -94,7 +90,7 @@ pub(crate) fn attach_codex_logical_turn_context(
|
||||
|
||||
pub(crate) fn restore_codex_logical_turn_context(
|
||||
parts: &mut Parts,
|
||||
context: &CodexFingerprintConvergenceContext,
|
||||
context: &ProviderOutboundRequestContext,
|
||||
) {
|
||||
parts.extensions.remove::<CodexFingerprintContextSlot>();
|
||||
parts.extensions.insert(context.clone());
|
||||
@@ -104,10 +100,9 @@ fn build_codex_fingerprint_context(
|
||||
headers: &HeaderMap,
|
||||
body_json: &Value,
|
||||
logical_turn_id: String,
|
||||
) -> CodexFingerprintConvergenceContext {
|
||||
) -> ProviderOutboundRequestContext {
|
||||
let signals = codex_request_signals_from_request(headers, Some(body_json));
|
||||
let mut context =
|
||||
CodexFingerprintConvergenceContext::new(logical_turn_id, current_unix_millis());
|
||||
let mut context = ProviderOutboundRequestContext::new(logical_turn_id, current_unix_millis());
|
||||
|
||||
if let Some(turn_id) = signals.turn_id {
|
||||
context = context.with_original_turn_id(turn_id);
|
||||
@@ -160,14 +155,14 @@ mod tests {
|
||||
assert_eq!(context.original_client_session_id(), Some("header-thread"));
|
||||
assert_eq!(context.original_prompt_cache_key(), Some("client-cache"));
|
||||
assert_eq!(
|
||||
parts.extensions.get::<CodexFingerprintConvergenceContext>(),
|
||||
parts.extensions.get::<ProviderOutboundRequestContext>(),
|
||||
Some(&context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restored_context_wins_over_retry_request_signals() {
|
||||
let original = CodexFingerprintConvergenceContext::new("logical-turn", 1234)
|
||||
let original = ProviderOutboundRequestContext::new("logical-turn", 1234)
|
||||
.with_original_turn_id("original-turn")
|
||||
.with_original_client_session_id("original-thread")
|
||||
.with_original_prompt_cache_key("original-cache");
|
||||
|
||||
@@ -13,7 +13,7 @@ use http::{HeaderMap, HeaderName, HeaderValue};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::ai_serving::planner::common::extract_standard_requested_model;
|
||||
use crate::ai_serving::transport::CodexFingerprintConvergenceContext;
|
||||
use crate::ai_serving::transport::ProviderOutboundRequestContext;
|
||||
use crate::ai_serving::{
|
||||
ClientSurface, ExecutionRuntimeAuthContext, GatewayAuthApiKeySnapshot,
|
||||
GatewayCredentialCarrier, GatewayProviderTransportSnapshot, PlannerAppState,
|
||||
@@ -56,7 +56,7 @@ pub(crate) struct LocalRequestedModelDecisionInput {
|
||||
pub(crate) client_surface: Option<ClientSurface>,
|
||||
pub(crate) gateway_credential_carrier: Option<GatewayCredentialCarrier>,
|
||||
pub(crate) client_session_affinity: Option<ClientSessionAffinity>,
|
||||
pub(crate) codex_fingerprint_context: Option<CodexFingerprintConvergenceContext>,
|
||||
pub(crate) provider_outbound_context: Option<ProviderOutboundRequestContext>,
|
||||
pub(crate) routing_policy: Option<ResolvedRoutingPolicy>,
|
||||
pub(crate) routing_trace_seed: Option<RoutingDecisionTrace>,
|
||||
pub(crate) routing_context: Option<LocalRoutingRequestContext>,
|
||||
@@ -168,7 +168,7 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision_with_websocket_m
|
||||
provider_api_format.as_str(),
|
||||
);
|
||||
}
|
||||
apply_codex_fingerprint_convergence_to_decision(
|
||||
apply_provider_outbound_request_policies_to_decision(
|
||||
input,
|
||||
decision,
|
||||
transport,
|
||||
@@ -231,7 +231,7 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision_with_websocket_m
|
||||
provider_api_format.as_str(),
|
||||
);
|
||||
}
|
||||
apply_codex_fingerprint_convergence_to_decision(
|
||||
apply_provider_outbound_request_policies_to_decision(
|
||||
input,
|
||||
decision,
|
||||
transport,
|
||||
@@ -356,7 +356,7 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision_with_websocket_m
|
||||
if original_provider_request_body.is_some() {
|
||||
decision.provider_request_body = Some(provider_request_body);
|
||||
}
|
||||
apply_codex_fingerprint_convergence_to_decision(
|
||||
apply_provider_outbound_request_policies_to_decision(
|
||||
input,
|
||||
decision,
|
||||
transport,
|
||||
@@ -366,7 +366,7 @@ pub(crate) fn apply_provider_request_routing_policy_to_decision_with_websocket_m
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_codex_fingerprint_convergence_to_decision(
|
||||
fn apply_provider_outbound_request_policies_to_decision(
|
||||
input: &LocalRequestedModelDecisionInput,
|
||||
decision: &mut AiExecutionDecision,
|
||||
transport: Option<&GatewayProviderTransportSnapshot>,
|
||||
@@ -377,17 +377,17 @@ fn apply_codex_fingerprint_convergence_to_decision(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(context) = input.codex_fingerprint_context.as_ref() else {
|
||||
let Some(context) = input.provider_outbound_context.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let applied = crate::ai_serving::transport::apply_codex_fingerprint_convergence_with_context(
|
||||
let results = crate::ai_serving::transport::apply_provider_outbound_request_policies(
|
||||
transport,
|
||||
provider_api_format,
|
||||
context,
|
||||
&mut decision.provider_request_headers,
|
||||
provider_request_body,
|
||||
);
|
||||
if applied {
|
||||
if results.iter().any(|result| result.was_applied()) {
|
||||
decision.prompt_cache_key = provider_request_body
|
||||
.get("prompt_cache_key")
|
||||
.and_then(Value::as_str)
|
||||
@@ -395,6 +395,31 @@ fn apply_codex_fingerprint_convergence_to_decision(
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned);
|
||||
}
|
||||
if results.is_empty() {
|
||||
return;
|
||||
}
|
||||
for result in &results {
|
||||
tracing::debug!(
|
||||
event_name = "provider_outbound_policy_evaluated",
|
||||
log_type = "event",
|
||||
policy = ?result.policy,
|
||||
outcome = ?result.outcome,
|
||||
reason = ?result.reason,
|
||||
mutation_scope = ?result.mutation_scope,
|
||||
identity_scope = ?result.identity_scope,
|
||||
"provider outbound request policy evaluated"
|
||||
);
|
||||
}
|
||||
let Some(serde_json::Value::Object(report_context)) = decision.report_context.as_mut() else {
|
||||
return;
|
||||
};
|
||||
report_context.insert(
|
||||
"provider_outbound_policies".to_string(),
|
||||
serde_json::json!({
|
||||
"schema_version": 1,
|
||||
"results": results,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
struct GatewayAuthenticatedDecisionInputPort<'a> {
|
||||
@@ -483,7 +508,7 @@ pub(crate) fn build_local_requested_model_decision_input(
|
||||
client_surface: None,
|
||||
gateway_credential_carrier: None,
|
||||
client_session_affinity: None,
|
||||
codex_fingerprint_context: None,
|
||||
provider_outbound_context: None,
|
||||
routing_policy: None,
|
||||
routing_trace_seed: None,
|
||||
routing_context: None,
|
||||
@@ -498,7 +523,7 @@ pub(crate) async fn attach_routing_policy_to_local_requested_model_input(
|
||||
body_json: &Value,
|
||||
client_api_format: &str,
|
||||
) -> Result<(), GatewayError> {
|
||||
input.codex_fingerprint_context =
|
||||
input.provider_outbound_context =
|
||||
Some(crate::ai_serving::codex_context::resolve_codex_fingerprint_context(parts, body_json));
|
||||
let explicit_group = routing_header_value_str(&parts.headers, ROUTING_GROUP_HEADER);
|
||||
let selected_group = match state.routing_group_read_repository() {
|
||||
@@ -1325,7 +1350,7 @@ mod tests {
|
||||
client_surface: None,
|
||||
gateway_credential_carrier: None,
|
||||
client_session_affinity: None,
|
||||
codex_fingerprint_context: None,
|
||||
provider_outbound_context: None,
|
||||
routing_policy: None,
|
||||
routing_trace_seed: None,
|
||||
model_directive_policy: Default::default(),
|
||||
@@ -1574,7 +1599,7 @@ mod tests {
|
||||
client_surface: None,
|
||||
gateway_credential_carrier: None,
|
||||
client_session_affinity: None,
|
||||
codex_fingerprint_context: None,
|
||||
provider_outbound_context: None,
|
||||
routing_policy: None,
|
||||
routing_trace_seed: None,
|
||||
model_directive_policy: Default::default(),
|
||||
@@ -1644,7 +1669,7 @@ mod tests {
|
||||
client_surface: None,
|
||||
gateway_credential_carrier: None,
|
||||
client_session_affinity: None,
|
||||
codex_fingerprint_context: None,
|
||||
provider_outbound_context: None,
|
||||
routing_policy: None,
|
||||
routing_trace_seed: None,
|
||||
routing_context: None,
|
||||
@@ -1713,6 +1738,35 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_codex_provider_outbound_policies_are_terminal_noop() {
|
||||
let mut input = sample_decision_input();
|
||||
input.routing_context = None;
|
||||
input.provider_outbound_context = Some(ProviderOutboundRequestContext::new(
|
||||
"logical-turn",
|
||||
1_700_000_000_123,
|
||||
));
|
||||
let mut decision = sample_codex_fingerprint_decision();
|
||||
decision.provider_type = Some("openai".to_string());
|
||||
decision.provider_api_format = Some("openai:responses".to_string());
|
||||
decision.client_api_format = Some("openai:responses".to_string());
|
||||
let mut transport = sample_codex_fingerprint_transport();
|
||||
transport.provider.provider_type = "openai".to_string();
|
||||
|
||||
let original_headers = decision.provider_request_headers.clone();
|
||||
let original_body = decision.provider_request_body.clone();
|
||||
apply_provider_request_routing_policy_to_decision(&input, &mut decision, Some(&transport))
|
||||
.expect("non-Codex terminal finalization should succeed");
|
||||
|
||||
assert_eq!(decision.provider_request_headers, original_headers);
|
||||
assert_eq!(decision.provider_request_body, original_body);
|
||||
assert!(decision
|
||||
.report_context
|
||||
.as_ref()
|
||||
.and_then(|context| context.get("provider_outbound_policies"))
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_fingerprint_convergence_runs_at_every_provider_routing_success_exit() {
|
||||
let transport = sample_codex_fingerprint_transport();
|
||||
@@ -1729,8 +1783,8 @@ mod tests {
|
||||
});
|
||||
let mut with_mutation = sample_decision_input();
|
||||
for input in [&mut no_context, &mut empty_mutation, &mut with_mutation] {
|
||||
input.codex_fingerprint_context = Some(
|
||||
CodexFingerprintConvergenceContext::new(
|
||||
input.provider_outbound_context = Some(
|
||||
ProviderOutboundRequestContext::new(
|
||||
uuid::Uuid::new_v4().to_string(),
|
||||
1_756_668_000_000,
|
||||
)
|
||||
@@ -1799,6 +1853,25 @@ mod tests {
|
||||
);
|
||||
assert_eq!(body["client_metadata"]["x-codex-window-id"], window_id);
|
||||
|
||||
let policy_results = decision
|
||||
.report_context
|
||||
.as_ref()
|
||||
.and_then(|context| context.get("provider_outbound_policies"))
|
||||
.and_then(|policies| policies.get("results"))
|
||||
.and_then(Value::as_array)
|
||||
.expect("provider policy results");
|
||||
assert_eq!(
|
||||
policy_results.len(),
|
||||
1,
|
||||
"policy result count at {exit_name}"
|
||||
);
|
||||
assert_eq!(
|
||||
policy_results[0]["policy"],
|
||||
json!("codex_fingerprint_convergence")
|
||||
);
|
||||
assert_eq!(policy_results[0]["outcome"], json!("applied"));
|
||||
assert_eq!(policy_results[0]["reason"], json!("applied"));
|
||||
|
||||
let header_metadata: Value =
|
||||
serde_json::from_str(&decision.provider_request_headers["x-codex-turn-metadata"])
|
||||
.expect("header turn metadata");
|
||||
|
||||
@@ -377,7 +377,7 @@ mod tests {
|
||||
client_surface: None,
|
||||
gateway_credential_carrier: None,
|
||||
client_session_affinity: None,
|
||||
codex_fingerprint_context: None,
|
||||
provider_outbound_context: None,
|
||||
routing_policy: None,
|
||||
routing_trace_seed: None,
|
||||
routing_context: None,
|
||||
|
||||
@@ -2182,7 +2182,7 @@ mod tests {
|
||||
client_surface: None,
|
||||
gateway_credential_carrier: None,
|
||||
client_session_affinity: None,
|
||||
codex_fingerprint_context: None,
|
||||
provider_outbound_context: None,
|
||||
routing_policy: None,
|
||||
routing_trace_seed: None,
|
||||
routing_context: None,
|
||||
|
||||
@@ -62,7 +62,8 @@ pub(crate) use aether_provider_transport::{
|
||||
append_transport_diagnostics_to_value, apply_codex_fingerprint_convergence,
|
||||
apply_codex_fingerprint_convergence_with_context, apply_local_auth_config_header_overrides,
|
||||
apply_local_body_rules, apply_local_body_rules_with_request_headers, apply_local_header_rules,
|
||||
apply_local_header_rules_with_request_headers, apply_standard_provider_request_body_rules,
|
||||
apply_local_header_rules_with_request_headers, apply_provider_outbound_request_policies,
|
||||
apply_standard_provider_request_body_rules,
|
||||
apply_standard_provider_request_body_rules_with_request_headers,
|
||||
apply_transport_request_body_semantics, body_rules_are_locally_supported,
|
||||
body_rules_handle_path, body_rules_have_enabled_rules,
|
||||
@@ -112,7 +113,11 @@ pub(crate) use aether_provider_transport::{
|
||||
GeminiCliRequestAuthSupport, GeminiCliRequestAuthUnsupportedReason,
|
||||
GeminiCliRequestEnvelopeSupport, GeminiFilesHeadersInput, GeminiFilesRequestBodyError,
|
||||
GeminiFilesRequestBodyParts, GrokHeaderInput, LocalResolvedOAuthRequestAuth,
|
||||
ProviderOpenAiImageHeadersInput, ProviderVideoCreateFamily, ProviderVideoCreateHeadersInput,
|
||||
ProviderOpenAiImageHeadersInput, ProviderOutboundRequestContext,
|
||||
ProviderOutboundRequestIdentityScope, ProviderOutboundRequestMutationScope,
|
||||
ProviderOutboundRequestPolicy, ProviderOutboundRequestPolicyOutcome,
|
||||
ProviderOutboundRequestPolicyReason, ProviderOutboundRequestPolicyResult,
|
||||
ProviderVideoCreateFamily, ProviderVideoCreateHeadersInput,
|
||||
SameFormatProviderCompatibilityEdit, SameFormatProviderCompatibilityEditAction,
|
||||
SameFormatProviderFamily, SameFormatProviderHeadersInput, SameFormatProviderRequestBehavior,
|
||||
SameFormatProviderRequestBehaviorParams, SameFormatProviderRequestBodyInput,
|
||||
@@ -121,5 +126,5 @@ pub(crate) use aether_provider_transport::{
|
||||
StandardProviderRequestHeaders, StandardProviderRequestHeadersInput,
|
||||
TransportRequestBodySemanticsError, TransportRequestUrlParams, GEMINI_CLI_USER_AGENT,
|
||||
GEMINI_CLI_V1INTERNAL_ENVELOPE_NAME, GROK_CHAT_PATH, GROK_INTERNAL_HEADER,
|
||||
GROK_RATE_LIMITS_PATH, WINDSURF_ENVELOPE_NAME,
|
||||
GROK_RATE_LIMITS_PATH, PROVIDER_OUTBOUND_CONTEXT_MAX_VALUE_BYTES, WINDSURF_ENVELOPE_NAME,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user