Files
codex-proxy-rs/backend/crates/gateway-core/src/engine/execution.rs
T
8d9aaf0bbd feat(accounts): support upstream API key accounts (#107)
Add Responses-compatible upstream accounts with isolated credentials, transport,
model catalogs, atomic settings updates, and account management controls.

Co-authored-by: Zyy <zyycn@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
2026-09-16 18:04:41 +08:00

1462 lines
53 KiB
Rust

//! 数据面执行用例:认证、准入、路由、continuation、circuit 与会话生命周期。
use std::collections::BTreeSet;
use std::fmt;
use std::net::IpAddr;
use std::num::NonZeroU32;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use futures::{FutureExt, future::BoxFuture, pin_mut, select_biased};
use futures_timer::Delay;
use uuid::Uuid;
use crate::concurrency::{CapacityWait, ConcurrencyWaitBudget, ConcurrencyWaitQueue};
use crate::engine::admission::{
ClientAdmissionDecision, ClientAdmissionPort, ClientAdmissionRejection, ClientAdmissionRequest,
};
use crate::engine::budget::{ClientBudgetCharge, ClientBudgetPort};
use crate::engine::continuation::{
ContinuationBinding, NativeContinuationPin, NativeContinuationPort,
NativeContinuationStoreErrorKind, PreviousResponseId,
};
use crate::engine::coordinator::ResponseExecutionSession;
use crate::engine::probe::{
AccountProbe, AccountProbeError, AccountProbeErrorSource, AccountProbeRequest,
AccountProbeResult, AccountProbeUpstreamResponse,
};
use crate::engine::provider::ProviderRegistry;
use crate::engine::{
AttemptCoordinator, AttemptRecord, CoordinatedEvent, EngineError, ExecutionStore,
GatewayEngine, IntermediateFailure, ModelRequestFinalization, ModelRequestId, NewModelRequest,
ProbeFailure, ProviderAccountId, ProviderAttemptOutcome, RecoveryReport, UpstreamSendState,
};
use crate::error::{GatewayError, GatewayErrorKind, ProviderErrorKind, StoreError};
use crate::event::{GatewayEvent, ProviderEvent, ProviderResponseHeader};
use crate::identity::ProviderKind;
use crate::lifecycle::CancellationToken;
use crate::operation::{Operation, ProviderSessionState};
use crate::policy::{ClientApiKeyId, ClientPolicy};
use crate::routing::{
ProviderCatalogUnavailable, PublicModelDescriptor, PublicModelId, RoutingContext,
RuntimeSnapshot, UpstreamModelId,
};
use crate::runtime::RuntimeSnapshotHandle;
const MODEL_REQUEST_DEADLINE: Duration = Duration::from_secs(10 * 60);
const COORDINATION_TIMEOUT: Duration = Duration::from_millis(100);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClientTransport {
HttpJson,
HttpSse,
WebSocket,
InternalProbe,
}
impl ClientTransport {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::HttpJson => "http_json",
Self::HttpSse => "http_sse",
Self::WebSocket => "websocket",
Self::InternalProbe => "internal",
}
}
}
/// API 解码后交给 Core 的稳定请求元数据。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExecutionRequestMetadata {
pub protocol: String,
pub endpoint: String,
pub transport: ClientTransport,
pub stream: bool,
pub client_ip: Option<IpAddr>,
pub user_agent: Option<String>,
pub previous_response_id: Option<PreviousResponseId>,
}
#[derive(Clone)]
pub struct AuthenticatedClient {
snapshot: Arc<RuntimeSnapshot>,
policy: ClientPolicy,
}
impl AuthenticatedClient {
#[must_use]
pub const fn snapshot(&self) -> &Arc<RuntimeSnapshot> {
&self.snapshot
}
#[must_use]
pub const fn policy(&self) -> &ClientPolicy {
&self.policy
}
}
impl fmt::Debug for AuthenticatedClient {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AuthenticatedClient")
.field("key_id", &self.policy.key_id())
.field("revision", &self.snapshot.revision())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ClientAuthenticationError {
#[error("client API key is invalid")]
InvalidKey,
#[error("runtime snapshot is unavailable")]
SnapshotUnavailable,
}
pub struct StartExecution {
pub client: AuthenticatedClient,
pub public_model: PublicModelId,
pub operation: Operation,
pub metadata: ExecutionRequestMetadata,
}
/// 启动一个由协议 adapter 明确绑定到 Provider 自有端点的请求。
pub struct StartProviderExecution {
pub client: AuthenticatedClient,
pub provider: ProviderKind,
pub operation: Operation,
pub metadata: ExecutionRequestMetadata,
}
enum ExecutionTarget {
Model(PublicModelId),
ProviderEndpoint(ProviderKind),
}
impl ExecutionTarget {
fn into_public_model(self) -> Option<PublicModelId> {
match self {
Self::Model(model) => Some(model),
Self::ProviderEndpoint(_) => None,
}
}
}
struct PendingStartExecution {
client: AuthenticatedClient,
target: ExecutionTarget,
operation: Operation,
metadata: ExecutionRequestMetadata,
}
pub struct StartedExecution {
pub request_id: ModelRequestId,
pub created_at: SystemTime,
pub stream: bool,
pub session: Box<dyn ExecutionSession>,
}
pub trait ExecutionSession: Send {
fn trace(&self) -> crate::diagnostics::TraceContext {
crate::diagnostics::TraceContext::default()
}
fn next_event(&mut self) -> BoxFuture<'_, Result<Option<CoordinatedEvent>, EngineError>>;
fn collect_uncommitted(&mut self) -> BoxFuture<'_, Result<Vec<ProviderEvent>, EngineError>>;
fn response_headers(&self) -> &[ProviderResponseHeader];
fn response_status_code(&self) -> Option<u16> {
None
}
fn commit_downstream(
&mut self,
client_status_code: Option<u16>,
) -> BoxFuture<'_, Result<(), EngineError>>;
fn record_client_status(
&mut self,
client_status_code: u16,
) -> BoxFuture<'_, Result<(), EngineError>>;
/// 执行已终结且结算、准入释放均已返回,协议层才可以放弃清理责任。
/// 结算失败仍由 Store 保留费用重试,释放失败仍按租约 TTL 收敛。
fn is_finalized(&self) -> bool;
fn cancel(&self);
/// 将会话交给宿主持续驱动清理;取消请求事件的等待不会丢弃已开始的结算。
fn detach_finalize(self: Box<Self>) -> BoxFuture<'static, ()>;
}
pub trait ExecutionService: Send + Sync {
fn authenticate(
&self,
plaintext: &str,
) -> Result<AuthenticatedClient, ClientAuthenticationError>;
fn public_models(&self, client: &AuthenticatedClient) -> Vec<PublicModelId>;
fn client_model_catalog<'a>(
&'a self,
_client: &'a AuthenticatedClient,
_protocol: &'a str,
_client_version: &'a str,
) -> BoxFuture<'a, Result<Vec<PublicModelDescriptor>, ProviderCatalogUnavailable>> {
Box::pin(async { Err(ProviderCatalogUnavailable) })
}
fn contains_public_model(&self, client: &AuthenticatedClient, model: &PublicModelId) -> bool;
fn start(
&self,
request: StartExecution,
) -> BoxFuture<'_, Result<StartedExecution, GatewayError>>;
fn start_provider_endpoint(
&self,
request: StartProviderExecution,
) -> BoxFuture<'_, Result<StartedExecution, GatewayError>>;
}
/// 只验证 Client API Key 并返回稳定 Key ID,不产生请求使用事实。
///
/// 管理侧登录等非数据面场景必须使用该端口,避免把认证本身误记为一次 Key 使用。
pub trait ClientKeyVerifier: Send + Sync {
fn verify_client_key(
&self,
plaintext: &str,
) -> Result<ClientApiKeyId, ClientAuthenticationError>;
}
/// 成功认证后的 API Key 使用事实接收器。
///
/// 认证仍是同步快照读取;实现必须自行异步、去重地持久化,不得阻塞客户端请求。
pub trait ClientApiKeyUsageSink: Send + Sync {
fn record_used(&self, key_id: &ClientApiKeyId);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderCircuitDecision {
Allow,
BlockedUntil(SystemTime),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("provider circuit store is unavailable")]
pub struct ProviderCircuitError;
/// Provider circuit 的可重建协调策略;由 Core 拥有并交给 Store adapter 执行。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProviderCircuitPolicy {
pub failure_threshold: NonZeroU32,
pub open_duration: Duration,
}
impl Default for ProviderCircuitPolicy {
fn default() -> Self {
Self {
failure_threshold: NonZeroU32::new(3).unwrap_or(NonZeroU32::MIN),
open_duration: Duration::from_secs(30),
}
}
}
pub trait ProviderCircuitPort: Send + Sync {
fn decision<'a>(
&'a self,
provider_kind: &'a ProviderKind,
) -> BoxFuture<'a, Result<ProviderCircuitDecision, ProviderCircuitError>>;
fn observe_failure<'a>(
&'a self,
provider_kind: &'a ProviderKind,
) -> BoxFuture<'a, Result<(), ProviderCircuitError>>;
fn observe_success<'a>(
&'a self,
provider_kind: &'a ProviderKind,
) -> BoxFuture<'a, Result<(), ProviderCircuitError>>;
}
pub struct DefaultExecutionService {
snapshots: RuntimeSnapshotHandle,
coordinator: Arc<AttemptCoordinator<dyn ExecutionStore>>,
probe_coordinator: Arc<AttemptCoordinator<dyn ExecutionStore>>,
/// probe 自身走 transient store,探测失败仍写入持久 store 的 ops_events。
observations: Arc<dyn ExecutionStore>,
providers: ProviderRegistry,
admissions: Arc<dyn ClientAdmissionPort>,
admission_waiting: ConcurrencyWaitQueue<ClientApiKeyId>,
circuits: Arc<dyn ProviderCircuitPort>,
continuation: Arc<dyn NativeContinuationPort>,
client_api_key_usage: Arc<dyn ClientApiKeyUsageSink>,
budget: Option<Arc<dyn ClientBudgetPort>>,
}
impl DefaultExecutionService {
#[must_use]
pub fn new(
snapshots: RuntimeSnapshotHandle,
execution: Arc<dyn ExecutionStore>,
providers: ProviderRegistry,
admissions: Arc<dyn ClientAdmissionPort>,
circuits: Arc<dyn ProviderCircuitPort>,
continuation: Arc<dyn NativeContinuationPort>,
client_api_key_usage: Arc<dyn ClientApiKeyUsageSink>,
) -> Self {
let observations = Arc::clone(&execution);
let engine = GatewayEngine::<dyn ExecutionStore>::new(execution, providers.clone());
let transient: Arc<dyn ExecutionStore> = Arc::new(TransientExecutionStore);
let probe_engine = GatewayEngine::<dyn ExecutionStore>::new(transient, providers.clone());
Self {
snapshots,
coordinator: Arc::new(AttemptCoordinator::new(engine)),
probe_coordinator: Arc::new(AttemptCoordinator::new(probe_engine)),
observations,
providers,
admissions,
admission_waiting: ConcurrencyWaitQueue::default(),
circuits,
continuation,
client_api_key_usage,
budget: None,
}
}
#[must_use]
pub fn with_budget(mut self, budget: Arc<dyn ClientBudgetPort>) -> Self {
self.budget = Some(budget);
self
}
fn authenticate_without_usage(
&self,
plaintext: &str,
) -> Result<AuthenticatedClient, ClientAuthenticationError> {
let snapshot = self
.snapshots
.acquire()
.map_err(|_| ClientAuthenticationError::SnapshotUnavailable)?;
let policy = snapshot
.client_policies()
.filter(|policy| {
constant_time_equal(plaintext, policy.plaintext_key().expose_for_auth())
})
.find(|policy| policy.authorize().is_ok())
.cloned()
.ok_or(ClientAuthenticationError::InvalidKey)?;
Ok(AuthenticatedClient { snapshot, policy })
}
async fn start_inner(&self, request: StartExecution) -> Result<StartedExecution, GatewayError> {
let StartExecution {
client,
public_model,
operation,
metadata,
} = request;
self.start_inner_with_target(PendingStartExecution {
client,
target: ExecutionTarget::Model(public_model),
operation,
metadata,
})
.await
}
async fn start_provider_endpoint_inner(
&self,
request: StartProviderExecution,
) -> Result<StartedExecution, GatewayError> {
let StartProviderExecution {
client,
provider,
operation,
metadata,
} = request;
self.start_inner_with_target(PendingStartExecution {
client,
target: ExecutionTarget::ProviderEndpoint(provider),
operation,
metadata,
})
.await
}
async fn start_inner_with_target(
&self,
mut request: PendingStartExecution,
) -> Result<StartedExecution, GatewayError> {
// 长连接每次执行都重新鉴权并冻结当前策略,确保限额和授权变更对新请求生效。
request.client = self
.authenticate(request.client.policy.plaintext_key().expose_for_auth())
.map_err(|error| match error {
ClientAuthenticationError::InvalidKey => {
GatewayError::new(GatewayErrorKind::Unauthorized, "client API key is invalid")
}
ClientAuthenticationError::SnapshotUnavailable => GatewayError::new(
GatewayErrorKind::Internal,
"runtime snapshot is unavailable",
),
})?;
let started_at = SystemTime::now();
let deadline_at = started_at
.checked_add(MODEL_REQUEST_DEADLINE)
.ok_or_else(|| {
GatewayError::new(GatewayErrorKind::Internal, "system clock is invalid")
})?;
let request_id = new_request_id()?;
let routing_context = self
.route_context(request.client.policy.account_scope().provider_kinds())
.await?;
let account_scope = Arc::clone(request.client.policy.account_scope());
let plan = match &request.target {
ExecutionTarget::ProviderEndpoint(provider) => {
request.client.snapshot.plan_provider_endpoint(
provider,
&request.operation,
account_scope,
&routing_context,
)
}
ExecutionTarget::Model(public_model) => request.client.snapshot.plan(
public_model,
&request.operation,
account_scope,
&routing_context,
),
}
.map_err(map_routing_error)?;
let continuation = match request.metadata.previous_response_id.as_ref() {
Some(previous) => {
let resolve = self
.continuation
.resolve(request.client.policy.key_id(), previous)
.fuse();
let timeout = Delay::new(COORDINATION_TIMEOUT).fuse();
pin_mut!(resolve, timeout);
let pin = select_biased! {
result = resolve => match result {
Ok(pin) => pin,
Err(error)
if error.kind() == NativeContinuationStoreErrorKind::Unavailable =>
{
tracing::debug!(
request_id = request_id.as_str(),
%error,
"Continuation affinity 查询失败,退化为外部续接"
);
None
}
Err(error)
if error.kind()
== NativeContinuationStoreErrorKind::OwnershipMismatch =>
{
return Err(GatewayError::new(
GatewayErrorKind::PolicyDenied,
"continuation does not belong to this client API key",
));
}
Err(error) => {
tracing::warn!(
request_id = request_id.as_str(),
%error,
"Continuation affinity 记录无效,已拒绝续接"
);
return Err(GatewayError::new(
GatewayErrorKind::Internal,
"continuation state is invalid",
));
}
},
_ = timeout => None,
};
match pin {
Some(pin) if !pin.matches_client(request.client.policy.key_id()) => {
return Err(GatewayError::new(
GatewayErrorKind::PolicyDenied,
"continuation does not belong to this client API key",
));
}
Some(pin) if !request.client.policy.account_scope().allows(pin.account()) => {
return Err(GatewayError::new(
GatewayErrorKind::PolicyDenied,
"continuation account is outside the client account scope",
));
}
Some(pin)
if !plan
.candidates()
.iter()
.any(|candidate| candidate.provider() == pin.provider()) =>
{
return Err(GatewayError::new(
GatewayErrorKind::NoAvailableProvider,
"continuation provider is not available",
));
}
Some(pin) => {
attach_continuation_session_state(&mut request.operation, &pin);
ContinuationBinding::Pinned(pin)
}
None => ContinuationBinding::External(previous.clone()),
}
}
None => {
return self
.start_without_continuation(
request,
request_id,
started_at,
deadline_at,
plan,
None,
)
.await;
}
};
self.start_without_continuation(
request,
request_id,
started_at,
deadline_at,
plan,
Some(continuation),
)
.await
}
async fn start_without_continuation(
&self,
request: PendingStartExecution,
request_id: ModelRequestId,
started_at: SystemTime,
deadline_at: SystemTime,
plan: crate::routing::RoutingPlan,
continuation: Option<ContinuationBinding>,
) -> Result<StartedExecution, GatewayError> {
let PendingStartExecution {
client,
target,
operation,
metadata,
} = request;
let admission_started_at = Instant::now();
let concurrency_wait_budget = ConcurrencyWaitBudget::default();
let admission = self
.acquire_client_admission(&client, &request_id, deadline_at, &concurrency_wait_budget)
.await?;
let admission_decision_ms = duration_ms(admission_started_at.elapsed());
if let Some(budget) = &self.budget
&& let Err(error) = budget.admit(client.policy.key_id().clone()).await
{
admission.release().await;
return Err(error);
}
let observation = plan
.candidates()
.first()
.map_or_else(Default::default, |candidate| {
self.providers.request_observation(
candidate.provider(),
&operation,
client.policy.key_id(),
)
});
let new_request = NewModelRequest {
id: request_id.clone(),
client_api_key_id: Some(client.policy.key_id().clone()),
client_api_key_ref: client.policy.key_id().clone(),
config_revision: plan.config_revision(),
routing: client.policy.account_scope().routing_snapshot(),
protocol: metadata.protocol,
operation: operation.kind(),
endpoint: metadata.endpoint,
client_transport: metadata.transport.as_str().to_owned(),
requested_model: target.into_public_model().or(observation.requested_model),
client_ip: metadata.client_ip,
user_agent: metadata.user_agent,
reasoning_effort: observation.reasoning_effort,
reasoning_preset: observation.reasoning_preset,
request_kind: observation.request_kind,
subagent_kind: observation.subagent_kind,
compact: observation.compact,
continuation: observation.continuation,
image_generation_requested: operation.image_generation_requested(),
admission_decision_ms: Some(admission_decision_ms),
started_at,
deadline_at,
};
let core = match self
.coordinator
.start(
new_request,
operation,
plan,
None,
continuation,
CancellationToken::new(),
)
.await
{
Ok(core) => core.with_concurrency_wait_budget(concurrency_wait_budget),
Err(error) => {
if let Some(budget) = &self.budget {
settle_budget(
budget.as_ref(),
ClientBudgetCharge {
key_id: client.policy.key_id().clone(),
request_id: request_id.clone(),
amount_usd: crate::metering::Decimal::ZERO,
completed_at: SystemTime::now(),
},
)
.await;
}
admission.release().await;
return Err(gateway_error_from_engine(&error));
}
};
Ok(StartedExecution {
request_id,
created_at: started_at,
stream: metadata.stream,
session: Box::new(DefaultExecutionSession::new(
core,
admission,
Arc::clone(&self.circuits),
Arc::clone(&self.continuation),
self.budget.clone(),
)),
})
}
async fn acquire_client_admission(
&self,
client: &AuthenticatedClient,
request_id: &ModelRequestId,
deadline_at: SystemTime,
budget: &ConcurrencyWaitBudget,
) -> Result<AdmissionLease, GatewayError> {
let policy = client.snapshot.client_queue_policy();
let limits = client.policy.limits();
let key = client.policy.key_id();
let mut waiting = CapacityWait::new(&self.admission_waiting, policy, deadline_at, budget);
let mut admission = AdmissionLease {
port: Arc::clone(&self.admissions),
client_api_key_id: key.clone(),
model_request_id: request_id.clone(),
armed: false,
};
loop {
let remaining = deadline_at
.duration_since(SystemTime::now())
.unwrap_or_default();
if remaining.is_zero() {
return Err(GatewayError::new(
GatewayErrorKind::Timeout,
"request deadline elapsed",
));
}
// 在发送原子准入之前接管取消清理,覆盖 Redis 已取得租约但返回尚未被观察的窗口。
admission.armed = true;
let acquire = self
.admissions
.admit(ClientAdmissionRequest {
model_request_id: request_id.clone(),
client_api_key_id: key.clone(),
lease_ttl: remaining,
allow_concurrency_acquire: limits.max_concurrency == 0 || waiting.can_try(key),
limits,
})
.fuse();
let timeout = Delay::new(remaining).fuse();
pin_mut!(acquire, timeout);
let decision = select_biased! {
result = acquire => result.map_err(|_| GatewayError::new(GatewayErrorKind::NoAvailableProvider, "request admission is temporarily unavailable"))?,
_ = timeout => return Err(GatewayError::new(GatewayErrorKind::Timeout, "request deadline elapsed")),
};
match decision {
ClientAdmissionDecision::Granted => {
if !waiting.elapsed().is_zero() {
tracing::info!(
request_id = request_id.as_str(),
queue_layer = "client_key",
queue_wait_ms = duration_ms(waiting.elapsed()),
"排队请求已取得 Key 并发槽位"
);
}
return Ok(admission);
}
ClientAdmissionDecision::Rejected(reason) => {
admission.armed = false;
if reason == ClientAdmissionRejection::RateLimited || policy.max_waiting == 0 {
return Err(GatewayError::new(
GatewayErrorKind::RateLimited,
"request exceeds client API key limits",
));
}
if waiting.elapsed().is_zero()
&& let Some(budget) = &self.budget
{
budget.admit(key.clone()).await?;
}
waiting.wait(std::slice::from_ref(key)).await.map_err(|error| {
tracing::info!(request_id = request_id.as_str(), queue_layer = "client_key", queue_wait_ms = duration_ms(waiting.elapsed()), reason = %error, "Key 排队请求被拒绝");
error.gateway_error()
})?;
}
}
}
}
async fn route_context(
&self,
provider_kinds: &BTreeSet<ProviderKind>,
) -> Result<RoutingContext, GatewayError> {
let decisions = futures::future::join_all(provider_kinds.iter().map(|provider_kind| {
let circuits = Arc::clone(&self.circuits);
async move {
let decision = circuits.decision(provider_kind).fuse();
let timeout = Delay::new(COORDINATION_TIMEOUT).fuse();
pin_mut!(decision, timeout);
let decision = select_biased! {
result = decision => Some(result),
_ = timeout => None,
};
(provider_kind, decision)
}
}))
.await;
let mut blocked_providers = BTreeSet::new();
for (provider_kind, decision) in decisions {
match decision {
Some(Ok(ProviderCircuitDecision::BlockedUntil(_))) => {
blocked_providers.insert(provider_kind.clone());
}
Some(Err(error)) => tracing::warn!(
provider = provider_kind.as_str(),
%error,
"Provider circuit 读取失败,按可重建协调状态 fail-open"
),
None => tracing::warn!(
provider = provider_kind.as_str(),
"Provider circuit 读取超时,按可重建协调状态 fail-open"
),
Some(Ok(ProviderCircuitDecision::Allow)) => {}
}
}
Ok(RoutingContext {
required_provider: None,
blocked_providers,
})
}
async fn probe_inner(
&self,
request: AccountProbeRequest,
) -> Result<AccountProbeResult, AccountProbeError> {
let AccountProbeRequest {
account_id,
provider_kind,
upstream_model,
operation,
} = request;
let observed = ProbeObservation {
provider_kind: provider_kind.clone(),
account_id: account_id.clone(),
upstream_model: upstream_model.clone(),
};
let snapshot = self.snapshots.acquire().map_err(|_| {
GatewayError::new(
GatewayErrorKind::Internal,
"runtime snapshot is unavailable",
)
})?;
let public_model =
PublicModelId::new(upstream_model.as_str().to_owned()).map_err(|_| {
GatewayError::new(GatewayErrorKind::Unsupported, "requested model is invalid")
})?;
let routing_context = RoutingContext {
required_provider: Some(provider_kind),
..RoutingContext::default()
};
let plan = snapshot
.plan(
&public_model,
&operation,
snapshot.all_account_scope(),
&routing_context,
)
.map_err(map_routing_error)?;
let started_at = SystemTime::now();
let deadline_at = started_at
.checked_add(MODEL_REQUEST_DEADLINE)
.ok_or_else(|| {
GatewayError::new(GatewayErrorKind::Internal, "system clock is invalid")
})?;
let request_id = new_request_id()?;
let actor = ClientApiKeyId::new("admin_connection_test")
.map_err(|_| GatewayError::new(GatewayErrorKind::Internal, "invalid admin actor"))?;
let new_request = NewModelRequest {
id: request_id,
client_api_key_id: None,
client_api_key_ref: actor,
config_revision: plan.config_revision(),
routing: crate::routing::AccountRoutingSnapshot::all(),
protocol: "admin_connection_test".to_owned(),
operation: operation.kind(),
endpoint: "/api/admin/accounts/connection-test".to_owned(),
client_transport: ClientTransport::InternalProbe.as_str().to_owned(),
requested_model: Some(public_model),
client_ip: None,
user_agent: None,
reasoning_effort: None,
reasoning_preset: None,
request_kind: Some("account_connection_test".to_owned()),
subagent_kind: None,
compact: false,
continuation: Default::default(),
image_generation_requested: false,
admission_decision_ms: None,
started_at,
deadline_at,
};
let mut session = match self
.probe_coordinator
.start_diagnostic(
new_request,
operation,
plan,
account_id,
None,
CancellationToken::new(),
)
.await
{
Ok(session) => session,
Err(error) => {
return Err(self
.observe_probe_failure(&observed, started_at, &error)
.await);
}
};
let events = session.collect_uncommitted().await;
publish_provider_attempt_outcomes(
self.circuits.as_ref(),
session.provider_attempt_outcomes(),
)
.await;
let events = match events {
Ok(events) => events,
Err(error) => {
return Err(self
.observe_probe_failure(&observed, started_at, &error)
.await);
}
};
if let Err(error) = session.commit_downstream(Some(200)).await {
return Err(self
.observe_probe_failure(&observed, started_at, &error)
.await);
}
Ok(AccountProbeResult {
text: events
.into_iter()
.flat_map(|event| event.into_parts().0)
.filter_map(|fact| match fact {
GatewayEvent::TextDelta(delta) => Some(delta.text),
_ => None,
})
.collect(),
})
}
/// 探测失败先记录脱敏分类事实,再把请求局部的原始上游响应交给认证管理端。
async fn observe_probe_failure(
&self,
observed: &ProbeObservation,
started_at: SystemTime,
error: &EngineError,
) -> AccountProbeError {
let (source, send_state, upstream_response) = match error {
EngineError::Provider(provider_error) => {
let upstream_response = provider_error
.client_visible_upstream_response()
.map(AccountProbeUpstreamResponse::from_client_response);
let has_upstream_facts = provider_error.send_state() != UpstreamSendState::NotSent
|| provider_error.upstream_status().is_some()
|| provider_error.upstream_code().is_some()
|| provider_error.client_visible_upstream_error().is_some()
|| upstream_response.is_some();
let source = if has_upstream_facts {
AccountProbeErrorSource::Upstream
} else {
AccountProbeErrorSource::Provider
};
(source, Some(provider_error.send_state()), upstream_response)
}
_ => (AccountProbeErrorSource::Gateway, None, None),
};
if let EngineError::Provider(provider_error) = error {
let latency = started_at.elapsed().unwrap_or_default();
let latency_ms = u64::try_from(latency.as_millis()).unwrap_or(u64::MAX);
tracing::warn!(
target: "gateway_probe",
provider_kind = observed.provider_kind.as_str(),
account_id = observed.account_id.as_str(),
upstream_model = observed.upstream_model.as_str(),
failure_kind = provider_error.kind().as_str(),
send_state = ?provider_error.send_state(),
upstream_status = ?provider_error.upstream_status(),
provider_error_code = ?provider_error.upstream_code().map(|code| code.as_str()),
latency_ms,
"账号连接测试失败"
);
if let Err(store_error) = self
.observations
.record_probe_failure(ProbeFailure {
provider_kind: observed.provider_kind.clone(),
account_id: observed.account_id.clone(),
upstream_model_id: observed.upstream_model.clone(),
error: provider_error.clone(),
latency,
})
.await
{
tracing::warn!(
operation = "record_probe_failure",
provider_kind = observed.provider_kind.as_str(),
account_id = observed.account_id.as_str(),
error_kind = ?store_error.kind(),
"账号连接测试观测写入失败,测试结果不受影响"
);
}
}
AccountProbeError::new(
gateway_error_from_engine(error),
source,
send_state,
upstream_response,
)
}
}
struct ProbeObservation {
provider_kind: ProviderKind,
account_id: ProviderAccountId,
upstream_model: UpstreamModelId,
}
struct TransientExecutionStore;
#[async_trait::async_trait]
impl ExecutionStore for TransientExecutionStore {
async fn create_model_request(&self, _: NewModelRequest) -> Result<(), StoreError> {
Ok(())
}
async fn record_attempt(&self, _: AttemptRecord) -> Result<(), StoreError> {
Ok(())
}
async fn mark_send_state(
&self,
_: &ModelRequestId,
_: UpstreamSendState,
) -> Result<(), StoreError> {
Ok(())
}
async fn mark_downstream_committed(
&self,
_: &ModelRequestId,
_: SystemTime,
_: Option<u16>,
) -> Result<(), StoreError> {
Ok(())
}
async fn record_client_status(&self, _: &ModelRequestId, _: u16) -> Result<(), StoreError> {
Ok(())
}
async fn record_intermediate_failure(&self, _: IntermediateFailure) -> Result<(), StoreError> {
Ok(())
}
async fn finalize_model_request(&self, _: ModelRequestFinalization) -> Result<(), StoreError> {
Ok(())
}
async fn recover_expired(&self, _: SystemTime) -> Result<RecoveryReport, StoreError> {
Ok(RecoveryReport::default())
}
}
impl ExecutionService for DefaultExecutionService {
fn authenticate(
&self,
plaintext: &str,
) -> Result<AuthenticatedClient, ClientAuthenticationError> {
let client = self.authenticate_without_usage(plaintext)?;
self.client_api_key_usage
.record_used(client.policy().key_id());
Ok(client)
}
fn public_models(&self, client: &AuthenticatedClient) -> Vec<PublicModelId> {
client
.snapshot
.public_models_for_scope(client.policy.account_scope())
}
fn client_model_catalog<'a>(
&'a self,
client: &'a AuthenticatedClient,
protocol: &'a str,
client_version: &'a str,
) -> BoxFuture<'a, Result<Vec<PublicModelDescriptor>, ProviderCatalogUnavailable>> {
Box::pin(async move {
let scope = client.policy.account_scope();
let mut result = Vec::new();
let mut seen = BTreeSet::new();
for kind in scope.provider_kinds() {
let provider = self.providers.get(kind).ok_or(ProviderCatalogUnavailable)?;
let Some(models) = provider
.query_client_model_catalog(scope, protocol, client_version)
.await?
else {
for profile in client.snapshot.public_model_profiles_for_provider(kind) {
if !scope.allows_provider_model(
kind,
&client.snapshot.mapped_model(profile.model().as_str()),
) {
continue;
}
if seen.insert(profile.model().clone()) {
result.push(PublicModelDescriptor::Adapted(profile));
}
}
continue;
};
// 保留上游顺序;映射只选择完整的目标对象,不能跨账号/模型混拼字段。
let by_id = models
.iter()
.map(|entry| (entry.model.as_str(), entry))
.collect::<std::collections::BTreeMap<_, _>>();
for entry in &models {
let target = client.snapshot.mapped_model(entry.model.as_str());
if !scope.allows_provider_model(kind, &target) {
continue;
}
let Some(source) = by_id.get(target.as_str()) else {
continue;
};
let model = PublicModelId::new(entry.model.as_str().to_owned())
.map_err(|_| ProviderCatalogUnavailable)?;
// 原生目录可能比路由快照更新,不能向客户端公布当前已知不可路由的模型。
if !client
.snapshot
.contains_public_model_for_provider(&model, kind)
{
continue;
}
if seen.insert(model.clone()) {
result.push(source.content.for_public_model(model));
}
}
for model in client.snapshot.public_models_for_provider(kind) {
let target = client.snapshot.mapped_model(model.as_str());
if !scope.allows_provider_model(kind, &target) {
continue;
}
if target == model.as_str() || seen.contains(&model) {
continue;
}
if !client
.snapshot
.contains_public_model_for_provider(&model, kind)
{
continue;
}
if let Some(source) = by_id.get(target.as_str()) {
seen.insert(model.clone());
result.push(source.content.for_public_model(model));
}
}
}
Ok(result)
})
}
fn contains_public_model(&self, client: &AuthenticatedClient, model: &PublicModelId) -> bool {
client
.snapshot
.contains_public_model_for_scope(model, client.policy.account_scope())
}
fn start(
&self,
request: StartExecution,
) -> BoxFuture<'_, Result<StartedExecution, GatewayError>> {
Box::pin(async move { self.start_inner(request).await })
}
fn start_provider_endpoint(
&self,
request: StartProviderExecution,
) -> BoxFuture<'_, Result<StartedExecution, GatewayError>> {
Box::pin(async move { self.start_provider_endpoint_inner(request).await })
}
}
impl ClientKeyVerifier for DefaultExecutionService {
fn verify_client_key(
&self,
plaintext: &str,
) -> Result<ClientApiKeyId, ClientAuthenticationError> {
self.authenticate_without_usage(plaintext)
.map(|client| client.policy().key_id().clone())
}
}
impl AccountProbe for DefaultExecutionService {
fn probe(
&self,
request: AccountProbeRequest,
) -> BoxFuture<'_, Result<AccountProbeResult, AccountProbeError>> {
Box::pin(async move { self.probe_inner(request).await })
}
}
struct AdmissionLease {
armed: bool,
port: Arc<dyn ClientAdmissionPort>,
client_api_key_id: ClientApiKeyId,
model_request_id: ModelRequestId,
}
async fn settle_budget(port: &dyn ClientBudgetPort, charge: ClientBudgetCharge) {
if let Err(error) = port.settle(charge).await {
tracing::error!(%error, "Client budget settlement failed; storage will retry on the next request");
}
}
impl AdmissionLease {
async fn release(mut self) {
if let Err(error) = self
.port
.release(&self.client_api_key_id, &self.model_request_id)
.await
{
tracing::warn!(%error, "Client admission 释放失败,依赖租约 TTL 收敛");
}
self.armed = false;
}
}
impl Drop for AdmissionLease {
fn drop(&mut self) {
if self.armed {
self.port
.abandon(&self.client_api_key_id, &self.model_request_id);
}
}
}
struct DefaultExecutionSession {
core: ResponseExecutionSession<dyn ExecutionStore>,
admission: Option<AdmissionLease>,
cleanup: Option<BoxFuture<'static, ()>>,
circuits: Arc<dyn ProviderCircuitPort>,
continuation: Arc<dyn NativeContinuationPort>,
observed_provider_outcomes: usize,
continuation_recorded: bool,
budget: Option<Arc<dyn ClientBudgetPort>>,
}
impl DefaultExecutionSession {
fn new(
core: ResponseExecutionSession<dyn ExecutionStore>,
admission: AdmissionLease,
circuits: Arc<dyn ProviderCircuitPort>,
continuation: Arc<dyn NativeContinuationPort>,
budget: Option<Arc<dyn ClientBudgetPort>>,
) -> Self {
Self {
core,
admission: Some(admission),
cleanup: None,
circuits,
continuation,
observed_provider_outcomes: 0,
continuation_recorded: false,
budget,
}
}
async fn settle_if_finalized(&mut self) {
if self.core.is_finalized()
&& let Some(admission) = self.admission.take()
{
let budget = self.budget.take();
let charge = self.core.budget_charge();
// 在首次 await 前把完整清理责任留在会话内。事件等待被取消后,后续 poll
// 或 detach 继续同一个 future,既不丢失费用,也不重启已完成的结算。
self.cleanup = Some(Box::pin(async move {
if let Some(budget) = budget {
settle_budget(budget.as_ref(), charge).await;
}
admission.release().await;
}));
}
if let Some(cleanup) = self.cleanup.as_mut() {
cleanup.await;
self.cleanup = None;
}
}
async fn observe_provider_outcomes(&mut self) {
let outcomes = self.core.provider_attempt_outcomes();
let new_outcomes = outcomes
.get(self.observed_provider_outcomes..)
.unwrap_or_default()
.to_vec();
self.observed_provider_outcomes = outcomes.len();
publish_provider_attempt_outcomes(self.circuits.as_ref(), &new_outcomes).await;
}
async fn record_continuation(&mut self, state: Option<&ProviderSessionState>) {
if self.continuation_recorded {
return;
}
let Some(state) = state else {
return;
};
let Some(pin) = self.core.native_continuation_pin(state) else {
return;
};
self.continuation_recorded = true;
record_native_continuation(self.continuation.as_ref(), pin).await;
}
async fn finalize_detached(&mut self) {
if let Err(error) = self.core.cancel_and_finalize().await {
tracing::warn!(%error, "Detached execution 终态收敛失败");
}
self.observe_provider_outcomes().await;
self.settle_if_finalized().await;
}
}
impl Drop for DefaultExecutionSession {
fn drop(&mut self) {
self.core.cancel();
}
}
impl ExecutionSession for DefaultExecutionSession {
fn trace(&self) -> crate::diagnostics::TraceContext {
self.core.trace()
}
fn next_event(&mut self) -> BoxFuture<'_, Result<Option<CoordinatedEvent>, EngineError>> {
Box::pin(async move {
let result = self.core.next_event().await;
if let Ok(Some(event)) = result.as_ref() {
self.record_continuation(event.session_update()).await;
}
self.observe_provider_outcomes().await;
self.settle_if_finalized().await;
result
})
}
fn collect_uncommitted(&mut self) -> BoxFuture<'_, Result<Vec<ProviderEvent>, EngineError>> {
Box::pin(async move {
let result = self.core.collect_uncommitted().await;
if let Ok(events) = result.as_ref() {
let state = events.iter().find_map(ProviderEvent::session_update);
self.record_continuation(state).await;
}
self.observe_provider_outcomes().await;
self.settle_if_finalized().await;
result
})
}
fn response_headers(&self) -> &[ProviderResponseHeader] {
self.core.response_headers()
}
fn response_status_code(&self) -> Option<u16> {
self.core.response_status_code()
}
fn commit_downstream(
&mut self,
client_status_code: Option<u16>,
) -> BoxFuture<'_, Result<(), EngineError>> {
Box::pin(async move {
let result = self.core.commit_downstream(client_status_code).await;
self.observe_provider_outcomes().await;
self.settle_if_finalized().await;
result
})
}
fn record_client_status(
&mut self,
client_status_code: u16,
) -> BoxFuture<'_, Result<(), EngineError>> {
Box::pin(async move {
let result = self.core.record_client_status(client_status_code).await;
self.observe_provider_outcomes().await;
self.settle_if_finalized().await;
result
})
}
fn is_finalized(&self) -> bool {
self.core.is_finalized() && self.admission.is_none() && self.cleanup.is_none()
}
fn cancel(&self) {
self.core.cancel();
}
fn detach_finalize(mut self: Box<Self>) -> BoxFuture<'static, ()> {
Box::pin(async move { self.finalize_detached().await })
}
}
#[must_use]
pub const fn provider_failure_affects_circuit(error_kind: ProviderErrorKind) -> bool {
matches!(
error_kind,
ProviderErrorKind::Timeout
| ProviderErrorKind::Transport
| ProviderErrorKind::Protocol
| ProviderErrorKind::Unavailable
)
}
async fn publish_provider_attempt_outcomes(
circuits: &dyn ProviderCircuitPort,
outcomes: &[ProviderAttemptOutcome],
) {
for outcome in outcomes {
let result = match outcome.error_kind() {
None => circuits.observe_success(outcome.provider_kind()).await,
Some(kind) if provider_failure_affects_circuit(kind) => {
circuits.observe_failure(outcome.provider_kind()).await
}
Some(_) => continue,
};
if let Err(error) = result {
tracing::warn!(
provider = outcome.provider_kind().as_str(),
%error,
"Provider circuit feedback 写入失败,数据面不受影响"
);
}
}
}
async fn record_native_continuation(
continuation: &dyn NativeContinuationPort,
pin: NativeContinuationPin,
) {
let provider = pin.provider().as_str().to_owned();
let account = pin.account().as_str().to_owned();
let record = continuation.record(pin).fuse();
let timeout = Delay::new(COORDINATION_TIMEOUT).fuse();
pin_mut!(record, timeout);
select_biased! {
result = record => {
if let Err(error) = result {
tracing::warn!(
provider = %provider,
account = %account,
%error,
"Continuation affinity 写入失败,后续请求将退化为外部续接"
);
}
},
_ = timeout => {
tracing::warn!(
provider = %provider,
account = %account,
"Continuation affinity 后台写入超时,已丢弃本次亲和记录"
);
},
}
}
fn constant_time_equal(left: &str, right: &str) -> bool {
if left.len() != right.len() {
return false;
}
left.as_bytes()
.iter()
.zip(right.as_bytes())
.fold(0_u8, |difference, (left, right)| {
difference | (left ^ right)
})
== 0
}
fn duration_ms(duration: Duration) -> u64 {
u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
}
fn new_request_id() -> Result<ModelRequestId, GatewayError> {
ModelRequestId::new(format!("req_{}", Uuid::now_v7().simple()))
.map_err(|_| GatewayError::new(GatewayErrorKind::Internal, "failed to allocate request ID"))
}
fn map_routing_error(error: crate::validation::RoutingError) -> GatewayError {
match error {
crate::validation::RoutingError::ModelNotFound {
model,
mapped_model,
} => GatewayError::new(
GatewayErrorKind::ModelNotFound,
if model == mapped_model {
"the requested model was not found in the provider catalogs available to this API key; check the model name"
} else {
"the requested model maps to an upstream model that was not found in the provider catalogs available to this API key; check the configured model mapping"
},
),
crate::validation::RoutingError::NoCapableProvider { .. }
| crate::validation::RoutingError::NoCapableProviderEndpoint { .. }
| crate::validation::RoutingError::EmptyAccountScope => GatewayError::new(
GatewayErrorKind::NoAvailableProvider,
"no provider can execute this request",
),
_ => GatewayError::new(
GatewayErrorKind::Internal,
"runtime routing configuration is invalid",
),
}
}
fn attach_continuation_session_state(operation: &mut Operation, pin: &NativeContinuationPin) {
let Some(state) = pin.session_state() else {
return;
};
if state.provider() != pin.provider().as_str()
|| operation.provider_session_state(state.provider()).is_some()
{
return;
}
operation.set_provider_session_state(state.clone());
}
pub fn gateway_error_from_engine(error: &EngineError) -> GatewayError {
match error {
EngineError::Cancelled => {
GatewayError::new(GatewayErrorKind::Cancelled, "request was cancelled")
}
EngineError::Deadline => {
GatewayError::new(GatewayErrorKind::Timeout, "request deadline elapsed")
}
EngineError::Provider(provider) => GatewayError::from_provider(provider),
EngineError::EmptyRoutingPlan | EngineError::ProviderNotRegistered { .. } => {
GatewayError::new(
GatewayErrorKind::NoAvailableProvider,
"no provider is available",
)
}
_ => GatewayError::new(GatewayErrorKind::Internal, "request execution failed"),
}
}