mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-09 04:30:20 +08:00
style: apply cargo fmt across gateway and aether-ai crates
This commit is contained in:
@@ -97,10 +97,13 @@ impl AttemptProviderOutcome {
|
||||
}
|
||||
|
||||
pub(crate) const fn stream_timeout(self) -> bool {
|
||||
matches!(self, Self::Aborted {
|
||||
stream_timeout: true,
|
||||
..
|
||||
})
|
||||
matches!(
|
||||
self,
|
||||
Self::Aborted {
|
||||
stream_timeout: true,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) const fn is_terminal(self) -> bool {
|
||||
@@ -161,10 +164,9 @@ impl AttemptTerminalFacts {
|
||||
/// `None` 一致。
|
||||
pub(crate) const fn forced_error(self) -> Option<&'static str> {
|
||||
match (self.provider, self.delivery) {
|
||||
(
|
||||
AttemptProviderOutcome::Aborted { reason, .. },
|
||||
AttemptClientDelivery::Complete,
|
||||
) => Some(reason),
|
||||
(AttemptProviderOutcome::Aborted { reason, .. }, AttemptClientDelivery::Complete) => {
|
||||
Some(reason)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -352,16 +354,11 @@ pub(crate) const fn classify_attempt_settlement(
|
||||
},
|
||||
candidate_status,
|
||||
candidate_error,
|
||||
provider_effect: classify_attempt_provider_effect(
|
||||
void,
|
||||
projects_provider_failure,
|
||||
failed,
|
||||
),
|
||||
provider_effect: classify_attempt_provider_effect(void, projects_provider_failure, failed),
|
||||
submit_execution_report: !void,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 每一段记账 I/O 的等待上界。
|
||||
///
|
||||
/// WS 用 `Bounded(5s)`:relay loop 是单任务,一段慢依赖会拖住整条连接的收发。
|
||||
@@ -895,7 +892,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn the_recorded_reason_prefers_the_client_delivery_failure() {
|
||||
assert_eq!(
|
||||
@@ -934,7 +930,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// §1.6 结算表,逐行。
|
||||
#[test]
|
||||
fn settlement_table_row_provider_cancelled_is_void_regardless_of_delivery() {
|
||||
@@ -943,8 +938,13 @@ mod tests {
|
||||
AttemptClientDelivery::Aborted { reason: "gone" },
|
||||
] {
|
||||
for report_represents_failure in [false, true] {
|
||||
let settlement =
|
||||
settle(provider_cancelled(), delivery, report_represents_failure, true, false);
|
||||
let settlement = settle(
|
||||
provider_cancelled(),
|
||||
delivery,
|
||||
report_represents_failure,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
assert_eq!(
|
||||
settlement,
|
||||
AttemptSettlement {
|
||||
@@ -961,7 +961,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn settlement_table_row_aborted_provider_with_aborted_delivery_is_void() {
|
||||
let settlement = settle(
|
||||
@@ -986,10 +985,15 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn settlement_table_row_clean_provider_terminal_is_a_billed_success() {
|
||||
let settlement = settle(terminal(200), AttemptClientDelivery::Complete, false, true, false);
|
||||
let settlement = settle(
|
||||
terminal(200),
|
||||
AttemptClientDelivery::Complete,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
assert_eq!(
|
||||
settlement,
|
||||
AttemptSettlement {
|
||||
@@ -1003,12 +1007,17 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// 合法 `response.incomplete`:记账层判失败,但供应商工作正常,
|
||||
/// 不扣健康分、只释放 lease,并且账单照记。
|
||||
#[test]
|
||||
fn settlement_table_row_legitimate_incomplete_is_billed_without_provider_failure() {
|
||||
let settlement = settle(terminal(200), AttemptClientDelivery::Complete, true, true, false);
|
||||
let settlement = settle(
|
||||
terminal(200),
|
||||
AttemptClientDelivery::Complete,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
assert_eq!(
|
||||
settlement,
|
||||
AttemptSettlement {
|
||||
@@ -1022,11 +1031,13 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn settlement_table_row_provider_abort_projects_a_provider_failure() {
|
||||
let settlement = settle(
|
||||
aborted(502, "upstream WebSocket closed before provider terminal event"),
|
||||
aborted(
|
||||
502,
|
||||
"upstream WebSocket closed before provider terminal event",
|
||||
),
|
||||
AttemptClientDelivery::Complete,
|
||||
true,
|
||||
false,
|
||||
@@ -1045,7 +1056,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// ✱ 修正后的那一行:provider 终态已到达,客户端投递失败不再作废账单。
|
||||
///
|
||||
/// 供应商已经完成推理并消耗 token,客户端还能用 `previous_response_id`
|
||||
@@ -1075,7 +1085,13 @@ mod tests {
|
||||
);
|
||||
|
||||
// 除了 candidate 的错误分类,其余判定与「投递成功」完全一致。
|
||||
let delivered = settle(terminal(200), AttemptClientDelivery::Complete, false, true, false);
|
||||
let delivered = settle(
|
||||
terminal(200),
|
||||
AttemptClientDelivery::Complete,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
assert_eq!(settlement.status_code, delivered.status_code);
|
||||
assert_eq!(settlement.billing, delivered.billing);
|
||||
assert_eq!(settlement.candidate_status, delivered.candidate_status);
|
||||
@@ -1087,7 +1103,6 @@ mod tests {
|
||||
assert_ne!(settlement.candidate_error, delivered.candidate_error);
|
||||
}
|
||||
|
||||
|
||||
/// 供应商还没给出终态时,客户端投递失败仍然作废账单:这一轮确实没有产出。
|
||||
#[test]
|
||||
fn a_delivery_failure_without_a_provider_terminal_still_voids_the_bill() {
|
||||
@@ -1110,23 +1125,32 @@ mod tests {
|
||||
assert!(!settlement.submit_execution_report);
|
||||
}
|
||||
|
||||
|
||||
/// 供应商自己声明取消时,即使内容送到了客户端也不计费。
|
||||
#[test]
|
||||
fn a_provider_declared_cancellation_is_void_even_when_delivered() {
|
||||
let settlement =
|
||||
settle(provider_cancelled(), AttemptClientDelivery::Complete, false, true, false);
|
||||
let settlement = settle(
|
||||
provider_cancelled(),
|
||||
AttemptClientDelivery::Complete,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
assert_eq!(settlement.billing, AttemptBilling::Void);
|
||||
assert_eq!(settlement.candidate_error, AttemptCandidateError::Cancelled);
|
||||
}
|
||||
|
||||
|
||||
/// 记账层判 Success,但摘要没观察到 finish:现状会写出
|
||||
/// 「candidate=Success + error_type=stream_missing_terminal_event」,
|
||||
/// 所以状态与错误分类必须各自独立。
|
||||
#[test]
|
||||
fn a_missing_terminal_can_coexist_with_a_successful_candidate_status() {
|
||||
let settlement = settle(terminal(200), AttemptClientDelivery::Complete, false, false, false);
|
||||
let settlement = settle(
|
||||
terminal(200),
|
||||
AttemptClientDelivery::Complete,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
assert_eq!(settlement.candidate_status, AttemptCandidateStatus::Success);
|
||||
assert_eq!(
|
||||
settlement.candidate_error,
|
||||
@@ -1139,10 +1163,15 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn a_parser_error_projects_a_provider_failure_even_on_a_clean_status_code() {
|
||||
let settlement = settle(terminal(200), AttemptClientDelivery::Complete, true, true, true);
|
||||
let settlement = settle(
|
||||
terminal(200),
|
||||
AttemptClientDelivery::Complete,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
assert_eq!(
|
||||
settlement.provider_effect,
|
||||
AttemptProviderEffect::ProviderFailure
|
||||
@@ -1150,7 +1179,6 @@ mod tests {
|
||||
assert_eq!(settlement.billing, AttemptBilling::Billed);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn a_legitimate_incomplete_still_releases_the_pool_key_lease() {
|
||||
// 共享 usage 判定目前仍把 response.incomplete 记成终态失败,于是会出现
|
||||
@@ -1162,7 +1190,6 @@ mod tests {
|
||||
assert!(effect.releases_pool_key_lease());
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn every_provider_effect_releases_the_pool_key_lease() {
|
||||
for (cancelled, projects_provider_failure, failed, expected) in [
|
||||
@@ -1172,36 +1199,18 @@ mod tests {
|
||||
false,
|
||||
AttemptProviderEffect::ReleasePoolKeyLease,
|
||||
),
|
||||
(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
AttemptProviderEffect::ReleasePoolKeyLease,
|
||||
),
|
||||
(
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
AttemptProviderEffect::ProviderFailure,
|
||||
),
|
||||
(true, true, true, AttemptProviderEffect::ReleasePoolKeyLease),
|
||||
(false, true, true, AttemptProviderEffect::ProviderFailure),
|
||||
(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
AttemptProviderEffect::ReleasePoolKeyLease,
|
||||
),
|
||||
(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
AttemptProviderEffect::ProviderSuccess,
|
||||
),
|
||||
(false, false, false, AttemptProviderEffect::ProviderSuccess),
|
||||
] {
|
||||
let effect = classify_attempt_provider_effect(
|
||||
cancelled,
|
||||
projects_provider_failure,
|
||||
failed,
|
||||
);
|
||||
let effect =
|
||||
classify_attempt_provider_effect(cancelled, projects_provider_failure, failed);
|
||||
assert_eq!(
|
||||
effect, expected,
|
||||
"cancelled={cancelled} projects_provider_failure={projects_provider_failure} failed={failed}"
|
||||
@@ -1213,7 +1222,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 每一个结算分支都必须释放 lease:这条不变量跨越整张结算表。
|
||||
#[test]
|
||||
fn every_settlement_branch_releases_the_pool_key_lease() {
|
||||
@@ -1291,9 +1299,13 @@ mod stage_tests {
|
||||
if !effects_completed {
|
||||
let released = Arc::clone(&lease_released);
|
||||
let _ = guard
|
||||
.await_stage("trace", "pool_lease_release_after_effect_timeout", async move {
|
||||
released.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.await_stage(
|
||||
"trace",
|
||||
"pool_lease_release_after_effect_timeout",
|
||||
async move {
|
||||
released.store(true, Ordering::SeqCst);
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
assert!(
|
||||
@@ -1423,14 +1435,22 @@ mod stage_tests {
|
||||
Some("websocket_cancelled".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
candidate_error_fields(AttemptCandidateError::ClientDeliveryFailed, None, "write failed"),
|
||||
candidate_error_fields(
|
||||
AttemptCandidateError::ClientDeliveryFailed,
|
||||
None,
|
||||
"write failed"
|
||||
),
|
||||
(
|
||||
Some("client_delivery_failed".to_string()),
|
||||
Some("write failed".to_string())
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
candidate_error_fields(AttemptCandidateError::TerminalError, Some("parser"), "reason"),
|
||||
candidate_error_fields(
|
||||
AttemptCandidateError::TerminalError,
|
||||
Some("parser"),
|
||||
"reason"
|
||||
),
|
||||
(
|
||||
Some("stream_terminal_error".to_string()),
|
||||
Some("parser".to_string())
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::collections::BTreeMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) mod attempt_lifecycle;
|
||||
pub(crate) mod admission;
|
||||
pub(crate) mod attempt_lifecycle;
|
||||
mod chatgpt_web_image;
|
||||
mod constants;
|
||||
mod fallback;
|
||||
|
||||
@@ -570,4 +570,3 @@ async fn wait_for_connection_permit_loss(permit: Option<&aether_runtime::Admissi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,7 @@ mod tests {
|
||||
#[test]
|
||||
fn structured_events_reach_the_terminal_summary_without_sse_text() {
|
||||
let context = report_context();
|
||||
let created =
|
||||
json!({"type": "response.created", "response": {"id": "resp_ws", "model": "gpt-5-codex"}});
|
||||
let created = json!({"type": "response.created", "response": {"id": "resp_ws", "model": "gpt-5-codex"}});
|
||||
let completed = json!({
|
||||
"type": "response.completed",
|
||||
"response": {
|
||||
|
||||
@@ -191,8 +191,8 @@ mod tests {
|
||||
use super::super::request::{
|
||||
build_planning_parts, normalize_followup_response_create, planned_response_create_event,
|
||||
};
|
||||
use super::super::turn_state::LogicalTurn;
|
||||
use super::super::turn::prepare_responses_websocket_turn_decision;
|
||||
use super::super::turn_state::LogicalTurn;
|
||||
use super::{
|
||||
redact_responses_websocket_client_event, ResponsesWebSocketRedactionRestorer,
|
||||
ResponsesWebSocketTurnRedaction, MAX_RETAINED_TURN_REDACTION_SESSIONS,
|
||||
@@ -595,11 +595,7 @@ mod tests {
|
||||
let effective_event = redacted_client_event(&state, &decision).await;
|
||||
// 配额透明重试重放 LogicalTurn 里保存的事件,所以保存的必须
|
||||
// 已经是脱敏版,否则重试会把原文发给新的上游账号。
|
||||
let active = LogicalTurn::new(
|
||||
effective_event.clone(),
|
||||
2,
|
||||
"logical-turn-2".to_string(),
|
||||
);
|
||||
let active = LogicalTurn::new(effective_event.clone(), 2, "logical-turn-2".to_string());
|
||||
assert_redacted_json(&active.client_event, "quota retry replay event");
|
||||
|
||||
let template = decision_template(
|
||||
|
||||
@@ -535,14 +535,12 @@ mod tests {
|
||||
normalize_followup_response_create, planned_response_create_event,
|
||||
response_create_model_or_current,
|
||||
};
|
||||
use super::super::state::{
|
||||
BoundResponsesConnection, ExhaustedResponsesWebSocketExclusions,
|
||||
};
|
||||
use super::super::turn_state::{LogicalTurn, ResponsesTurnState};
|
||||
use super::super::state::{BoundResponsesConnection, ExhaustedResponsesWebSocketExclusions};
|
||||
use super::super::turn::{
|
||||
ResponsesWebSocketTurnDeadline, ResponsesWebSocketTurnObservation,
|
||||
ResponsesWebSocketTurnOutcome, ResponsesWebSocketTurnTimeoutPhase,
|
||||
};
|
||||
use super::super::turn_state::{LogicalTurn, ResponsesTurnState};
|
||||
use super::super::upstream::bind_responses_upstream;
|
||||
use crate::ai_serving::{AiExecutionDecision, ResponsesWebSocketBodyNormalization};
|
||||
use crate::handlers::proxy::websocket::session::wait_for_optional_deadline;
|
||||
|
||||
@@ -144,7 +144,11 @@ mod tests {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
attempt_facts_for_outcome(None, AttemptClientDelivery::Complete, ResponsesWebSocketTurnOutcome::upstream_closed()),
|
||||
attempt_facts_for_outcome(
|
||||
None,
|
||||
AttemptClientDelivery::Complete,
|
||||
ResponsesWebSocketTurnOutcome::upstream_closed()
|
||||
),
|
||||
AttemptTerminalFacts {
|
||||
provider: aborted(
|
||||
502,
|
||||
@@ -154,7 +158,11 @@ mod tests {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
attempt_facts_for_outcome(None, AttemptClientDelivery::Complete, ResponsesWebSocketTurnOutcome::client_disconnected()),
|
||||
attempt_facts_for_outcome(
|
||||
None,
|
||||
AttemptClientDelivery::Complete,
|
||||
ResponsesWebSocketTurnOutcome::client_disconnected()
|
||||
),
|
||||
AttemptTerminalFacts {
|
||||
provider: aborted(499, "client disconnected before provider terminal event"),
|
||||
delivery: AttemptClientDelivery::Aborted {
|
||||
@@ -164,11 +172,17 @@ mod tests {
|
||||
);
|
||||
|
||||
// 超时一族必须保留 stream_timeout 标记,否则 pool stream timeout 效果丢失。
|
||||
let first_event_timeout =
|
||||
attempt_facts_for_outcome(None, AttemptClientDelivery::Complete, ResponsesWebSocketTurnOutcome::first_event_timeout());
|
||||
let first_event_timeout = attempt_facts_for_outcome(
|
||||
None,
|
||||
AttemptClientDelivery::Complete,
|
||||
ResponsesWebSocketTurnOutcome::first_event_timeout(),
|
||||
);
|
||||
assert!(first_event_timeout.provider.stream_timeout());
|
||||
let terminal_timeout =
|
||||
attempt_facts_for_outcome(None, AttemptClientDelivery::Complete, ResponsesWebSocketTurnOutcome::terminal_timeout());
|
||||
let terminal_timeout = attempt_facts_for_outcome(
|
||||
None,
|
||||
AttemptClientDelivery::Complete,
|
||||
ResponsesWebSocketTurnOutcome::terminal_timeout(),
|
||||
);
|
||||
assert!(terminal_timeout.provider.stream_timeout());
|
||||
// 非 504 的失败不得被当成流式超时。
|
||||
assert!(!attempt_facts_for_outcome(
|
||||
@@ -192,7 +206,6 @@ mod tests {
|
||||
.stream_timeout());
|
||||
}
|
||||
|
||||
|
||||
/// `Cancelled` 不携带 provider 信息,已观察到的终态不能被它覆盖;
|
||||
/// `ProviderTerminal` / `Failure` 本身就是权威的 provider 事实。
|
||||
#[test]
|
||||
@@ -220,12 +233,14 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
facts.provider,
|
||||
aborted(502, "upstream WebSocket closed before provider terminal event")
|
||||
aborted(
|
||||
502,
|
||||
"upstream WebSocket closed before provider terminal event"
|
||||
)
|
||||
);
|
||||
assert_eq!(facts.delivery, AttemptClientDelivery::Complete);
|
||||
}
|
||||
|
||||
|
||||
/// 结算信号的选择:provider 终态已到达就用它,否则才是 client 断开。
|
||||
/// 这是修正的核心——旧实现无条件用 client_disconnected() 覆盖,
|
||||
/// 于是已完成的响应被记成 void billing。
|
||||
@@ -245,14 +260,13 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// 明确记录的投递失败不会被结算信号推出的「投递成功」覆盖。
|
||||
#[test]
|
||||
fn a_recorded_delivery_failure_survives_a_provider_terminal_settle_signal() {
|
||||
let facts = attempt_facts_for_outcome(
|
||||
Some(terminal(200)),
|
||||
AttemptClientDelivery::Aborted {
|
||||
reason: "write failed"
|
||||
reason: "write failed",
|
||||
},
|
||||
ResponsesWebSocketTurnOutcome::ProviderTerminal {
|
||||
status_code: 200,
|
||||
|
||||
@@ -104,4 +104,3 @@ impl ExhaustedResponsesWebSocketExclusions {
|
||||
.retain(|_, expires_at| *expires_at > now_unix_secs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,6 @@ use super::admission::ResponsesWebSocketTurnAdmission;
|
||||
use super::frame::ParsedResponsesWebSocketFrame;
|
||||
use super::observation::ResponsesStructuredTerminalObserver;
|
||||
use super::settlement::attempt_facts_for_outcome;
|
||||
use crate::execution_runtime::attempt_lifecycle::{
|
||||
attempt_billing_is_void, AttemptBodyCapture, AttemptClientDelivery, AttemptLifecycleSeed,
|
||||
AttemptProviderOutcome, AttemptStageGuard, AttemptTerminalFacts, AttemptTerminalFactsInput,
|
||||
ExecutionAttemptLifecycle,
|
||||
};
|
||||
use crate::ai_serving::{build_openai_responses_stream_plan_from_decision, AiExecutionDecision};
|
||||
use crate::clock::current_unix_ms;
|
||||
use crate::control::{
|
||||
@@ -47,6 +42,11 @@ use crate::control::{
|
||||
request_model_local_rejection, GatewayControlDecision, GatewayLocalAuthRejection,
|
||||
};
|
||||
use crate::execution_runtime::attach_provider_response_headers_to_report_context;
|
||||
use crate::execution_runtime::attempt_lifecycle::{
|
||||
attempt_billing_is_void, AttemptBodyCapture, AttemptClientDelivery, AttemptLifecycleSeed,
|
||||
AttemptProviderOutcome, AttemptStageGuard, AttemptTerminalFacts, AttemptTerminalFactsInput,
|
||||
ExecutionAttemptLifecycle,
|
||||
};
|
||||
use crate::orchestration::{
|
||||
apply_local_stream_failure_effects, apply_local_stream_success_effects,
|
||||
release_local_pool_key_lease, release_pool_key_lease_from_report_context,
|
||||
@@ -599,11 +599,9 @@ impl ResponsesProviderAttempt {
|
||||
if let Some(outcome) = provider_terminal_outcome(frame) {
|
||||
// provider 的终态是独立事实:先记下来,之后即使客户端投递失败、
|
||||
// 结算信号变成 Cancelled,这条事实也不会被擦掉。
|
||||
self.provider_outcome
|
||||
.get_or_insert(
|
||||
attempt_facts_for_outcome(None, AttemptClientDelivery::Complete, outcome)
|
||||
.provider,
|
||||
);
|
||||
self.provider_outcome.get_or_insert(
|
||||
attempt_facts_for_outcome(None, AttemptClientDelivery::Complete, outcome).provider,
|
||||
);
|
||||
return Some(ResponsesWebSocketTurnObservation::Terminal(outcome));
|
||||
}
|
||||
if frame.is_started() {
|
||||
@@ -668,8 +666,7 @@ impl ResponsesProviderAttempt {
|
||||
/// execution report)由共享的 [`ExecutionAttemptLifecycle::settle`] 负责,
|
||||
/// 这里只提供 WS 观察到的终态事实。
|
||||
async fn settle(mut self, state: &AppState, outcome: ResponsesWebSocketTurnOutcome) {
|
||||
let facts =
|
||||
attempt_facts_for_outcome(self.provider_outcome, self.client_delivery, outcome);
|
||||
let facts = attempt_facts_for_outcome(self.provider_outcome, self.client_delivery, outcome);
|
||||
if let Some(reason) = facts.delivery.aborted_reason() {
|
||||
let report_context = attach_client_delivery_to_report_context(
|
||||
self.lifecycle.take_report_context(),
|
||||
@@ -959,17 +956,16 @@ mod tests {
|
||||
use super::super::settlement::{
|
||||
attempt_facts_for_outcome, settle_signal_for_client_delivery_failure,
|
||||
};
|
||||
use super::{
|
||||
attach_client_delivery_to_report_context, prepare_websocket_report_context,
|
||||
provider_terminal_outcome, resolve_responses_websocket_turn_timeouts,
|
||||
websocket_event_as_sse_line, ResponsesWebSocketTurnDeadline, ResponsesWebSocketTurnOutcome,
|
||||
ResponsesWebSocketTurnTimeoutPhase,
|
||||
};
|
||||
use crate::execution_runtime::attempt_lifecycle::{
|
||||
classify_attempt_settlement, AttemptBilling, AttemptCandidateError, AttemptCandidateStatus,
|
||||
AttemptClientDelivery, AttemptProviderEffect, AttemptSettlementInputs,
|
||||
};
|
||||
use super::{
|
||||
attach_client_delivery_to_report_context, prepare_websocket_report_context,
|
||||
provider_terminal_outcome,
|
||||
resolve_responses_websocket_turn_timeouts, websocket_event_as_sse_line,
|
||||
ResponsesWebSocketTurnDeadline, ResponsesWebSocketTurnOutcome,
|
||||
ResponsesWebSocketTurnTimeoutPhase,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn followup_context_uses_a_fresh_request_and_candidate() {
|
||||
@@ -1194,7 +1190,10 @@ mod tests {
|
||||
reason: "gateway could not relay the provider event to the client",
|
||||
};
|
||||
let signal = settle_signal_for_client_delivery_failure(Some(observed));
|
||||
assert_eq!(signal, observed, "a reached terminal must remain the signal");
|
||||
assert_eq!(
|
||||
signal, observed,
|
||||
"a reached terminal must remain the signal"
|
||||
);
|
||||
|
||||
let facts = attempt_facts_for_outcome(Some(recorded_provider), delivery, signal);
|
||||
|
||||
@@ -1223,10 +1222,7 @@ mod tests {
|
||||
});
|
||||
assert_eq!(settlement.billing, AttemptBilling::Billed);
|
||||
assert_eq!(settlement.status_code, 200);
|
||||
assert_eq!(
|
||||
settlement.candidate_status,
|
||||
AttemptCandidateStatus::Success
|
||||
);
|
||||
assert_eq!(settlement.candidate_status, AttemptCandidateStatus::Success);
|
||||
assert_eq!(
|
||||
settlement.provider_effect,
|
||||
AttemptProviderEffect::ProviderSuccess
|
||||
|
||||
@@ -208,7 +208,11 @@ mod tests {
|
||||
state.begin(logical(), FakeAttempt(1));
|
||||
|
||||
let detached = state.detach_attempt();
|
||||
assert_eq!(detached, Some(FakeAttempt(1)), "the attempt is settled once");
|
||||
assert_eq!(
|
||||
detached,
|
||||
Some(FakeAttempt(1)),
|
||||
"the attempt is settled once"
|
||||
);
|
||||
assert!(matches!(state, ResponsesTurnState::Replanning { .. }));
|
||||
|
||||
// 结算已经发生,没有第二个 attempt 可交。
|
||||
@@ -250,7 +254,9 @@ mod tests {
|
||||
assert!(!state.accepts_new_response_create());
|
||||
assert!(state.attempt().is_none());
|
||||
assert_eq!(
|
||||
state.logical().map(|logical| logical.logical_turn_id.clone()),
|
||||
state
|
||||
.logical()
|
||||
.map(|logical| logical.logical_turn_id.clone()),
|
||||
Some("logical-turn".to_string())
|
||||
);
|
||||
// 幂等:已经没有 attempt 了,再取一次不会伪造一个出来。
|
||||
|
||||
@@ -24,9 +24,7 @@ const DEFAULT_UPSTREAM_HANDSHAKE_DEADLINE_MS: u64 = 30_000;
|
||||
|
||||
/// 从 decision.timeouts 推导实际 handshake 绝对 deadline。
|
||||
/// 取 first_byte_ms / total_ms / DEFAULT 三者中的最小正值。
|
||||
pub(super) fn resolve_upstream_handshake_deadline(
|
||||
decision: &AiExecutionDecision,
|
||||
) -> Duration {
|
||||
pub(super) fn resolve_upstream_handshake_deadline(decision: &AiExecutionDecision) -> Duration {
|
||||
let mut deadline_ms = DEFAULT_UPSTREAM_HANDSHAKE_DEADLINE_MS;
|
||||
if let Some(timeouts) = decision.timeouts.as_ref() {
|
||||
if let Some(first_byte_ms) = timeouts.first_byte_ms.filter(|v| *v > 0) {
|
||||
@@ -48,12 +46,10 @@ pub(super) async fn bind_responses_upstream(
|
||||
// 绝对 deadline:从此刻起必须在限定时间内完成握手 + 首条事件发送,
|
||||
// 防止慢 TLS / 慢 HTTP Upgrade 无限占用 connection permit。
|
||||
let handshake_deadline = resolve_upstream_handshake_deadline(decision);
|
||||
tokio::time::timeout(handshake_deadline, bind_responses_upstream_inner(
|
||||
decision,
|
||||
normalization,
|
||||
initial_event,
|
||||
adapter,
|
||||
))
|
||||
tokio::time::timeout(
|
||||
handshake_deadline,
|
||||
bind_responses_upstream_inner(decision, normalization, initial_event, adapter),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "responses_websocket_upstream_handshake_timeout")?
|
||||
}
|
||||
|
||||
@@ -2728,7 +2728,10 @@ mod structured_entry_tests {
|
||||
assert_entries_agree("unknown events", &events);
|
||||
let summary = summary_via_push_event(&events);
|
||||
assert!(summary.observed_finish);
|
||||
assert!(summary.unknown_event_count > 0, "unknown events are counted");
|
||||
assert!(
|
||||
summary.unknown_event_count > 0,
|
||||
"unknown events are counted"
|
||||
);
|
||||
}
|
||||
|
||||
/// 供应商声明的 service tier 通过两条入口都要落到摘要上。
|
||||
|
||||
@@ -268,7 +268,9 @@ async fn provider_quota_exhaustion_transparently_retries_onto_another_key() -> R
|
||||
// 客户端只应该看到重试之后那次成功的响应,看不到 429。
|
||||
let completed = receive_event(&mut client, "response.completed").await?;
|
||||
assert_eq!(
|
||||
completed.pointer("/response/status").and_then(Value::as_str),
|
||||
completed
|
||||
.pointer("/response/status")
|
||||
.and_then(Value::as_str),
|
||||
Some("completed")
|
||||
);
|
||||
|
||||
@@ -832,8 +834,8 @@ async fn run_mock_upstream(
|
||||
}
|
||||
UpstreamBehavior::QuotaExhaustedThenComplete => {
|
||||
if turn == 1 {
|
||||
let _ = send_mock_event(&mut socket, codex_quota_exhausted_error())
|
||||
.await;
|
||||
let _ =
|
||||
send_mock_event(&mut socket, codex_quota_exhausted_error()).await;
|
||||
break;
|
||||
}
|
||||
if send_mock_turn(&mut socket, &response_id).await.is_err() {
|
||||
|
||||
Reference in New Issue
Block a user