style: format quota and response changes

This commit is contained in:
zhefox
2026-09-02 15:40:40 +08:00
parent 77229943d1
commit b1bf7837cf
10 changed files with 73 additions and 67 deletions
@@ -4,8 +4,7 @@ use serde_json::{json, Map, Value};
use crate::formats::openai::namespace::NamespaceToolAliases; use crate::formats::openai::namespace::NamespaceToolAliases;
use crate::formats::openai::responses::{ use crate::formats::openai::responses::{
encode_gemini_tool_signature_carrier_with_direction, encode_gemini_tool_signature_carrier_with_direction, openai_responses_message_item_id,
openai_responses_message_item_id,
openai_responses_synthetic_reasoning_item_id, openai_responses_synthetic_reasoning_item_id,
response::{ response::{
ensure_modern_openai_responses_response_fields, openai_responses_current_timestamp, ensure_modern_openai_responses_response_fields, openai_responses_current_timestamp,
@@ -318,8 +318,7 @@ mod tests {
use super::{ use super::{
decode_gemini_tool_signature_carrier, encode_gemini_tool_signature_carrier_with_direction, decode_gemini_tool_signature_carrier, encode_gemini_tool_signature_carrier_with_direction,
normalize_openai_responses_message_item_ids, openai_responses_message_item_id, normalize_openai_responses_message_item_ids, openai_responses_message_item_id,
openai_responses_request_operation, openai_responses_request_operation, openai_responses_synthetic_reasoning_item_id,
openai_responses_synthetic_reasoning_item_id,
strip_incompatible_openai_responses_reasoning_items, strip_incompatible_openai_responses_reasoning_items,
strip_incompatible_openai_responses_reasoning_items_with_policy, strip_incompatible_openai_responses_reasoning_items_with_policy,
GeminiToolSignatureCarrierDirection, OpenAiResponsesReasoningReplayPolicy, GeminiToolSignatureCarrierDirection, OpenAiResponsesReasoningReplayPolicy,
@@ -414,14 +413,8 @@ mod tests {
#[test] #[test]
fn synthetic_message_item_ids_are_stable_and_start_with_msg() { fn synthetic_message_item_ids_are_stable_and_start_with_msg() {
let first = openai_responses_message_item_id( let first = openai_responses_message_item_id("1c938e58-32a8-4d28-9c34-538d78076895", 0);
"1c938e58-32a8-4d28-9c34-538d78076895", let second = openai_responses_message_item_id("1c938e58-32a8-4d28-9c34-538d78076895", 0);
0,
);
let second = openai_responses_message_item_id(
"1c938e58-32a8-4d28-9c34-538d78076895",
0,
);
let other = openai_responses_message_item_id("chatcmpl-123", 1); let other = openai_responses_message_item_id("chatcmpl-123", 1);
assert!(first.starts_with("msg_")); assert!(first.starts_with("msg_"));
@@ -442,7 +435,9 @@ mod tests {
assert_eq!(normalize_openai_responses_message_item_ids(&mut body), 1); assert_eq!(normalize_openai_responses_message_item_ids(&mut body), 1);
let input = body["input"].as_array().expect("input array"); let input = body["input"].as_array().expect("input array");
assert!(input[0]["id"].as_str().is_some_and(|id| id.starts_with("msg_"))); assert!(input[0]["id"]
.as_str()
.is_some_and(|id| id.starts_with("msg_")));
assert_eq!(input[1]["id"], "msg_provider_123"); assert_eq!(input[1]["id"], "msg_provider_123");
assert_eq!(input[2].get("id"), Some(&json!("legacy_call"))); assert_eq!(input[2].get("id"), Some(&json!("legacy_call")));
assert!(input[3].get("id").is_none()); assert!(input[3].get("id").is_none());
@@ -10,7 +10,6 @@ use aether_ai_formats::formats::conversion::response::{
use aether_ai_formats::formats::openai::responses::{ use aether_ai_formats::formats::openai::responses::{
openai_responses_message_item_id, openai_responses_synthetic_reasoning_item_id, openai_responses_message_item_id, openai_responses_synthetic_reasoning_item_id,
}; };
use aether_ai_formats::formats::openai::responses::response::ensure_modern_openai_responses_response_fields;
use aether_ai_formats::formats::registry::{convert_response, FormatContext, FormatError}; use aether_ai_formats::formats::registry::{convert_response, FormatContext, FormatError};
use aether_ai_formats::{ use aether_ai_formats::{
canonical_response_unknown_block_count, canonical_to_claude_response, canonical_response_unknown_block_count, canonical_to_claude_response,
+1 -2
View File
@@ -58,8 +58,7 @@ pub use formats::openai::responses::request::{
}; };
pub use formats::openai::responses::{ pub use formats::openai::responses::{
normalize_openai_responses_message_item_ids, openai_responses_message_item_id, normalize_openai_responses_message_item_ids, openai_responses_message_item_id,
openai_responses_request_operation, openai_responses_request_operation, openai_responses_synthetic_reasoning_item_id,
openai_responses_synthetic_reasoning_item_id,
strip_incompatible_openai_responses_reasoning_items, strip_incompatible_openai_responses_reasoning_items,
strip_incompatible_openai_responses_reasoning_items_with_policy, strip_incompatible_openai_responses_reasoning_items_with_policy,
OpenAiResponsesReasoningReplayPolicy, OPENAI_RESPONSES_OPERATION_COMPACT, OpenAiResponsesReasoningReplayPolicy, OPENAI_RESPONSES_OPERATION_COMPACT,
@@ -3,11 +3,11 @@ use std::collections::{BTreeMap, BTreeSet, VecDeque};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value}; use serde_json::{json, Map, Value};
use crate::formats::openai::responses::openai_responses_message_item_id;
use crate::formats::openai::responses::{ use crate::formats::openai::responses::{
decode_gemini_tool_signature_carrier, GeminiToolSignatureCarrierDirection, decode_gemini_tool_signature_carrier, GeminiToolSignatureCarrierDirection,
}; };
use crate::formats::openai::shared::map_thinking_budget_to_openai_reasoning_effort; use crate::formats::openai::shared::map_thinking_budget_to_openai_reasoning_effort;
use crate::formats::openai::responses::openai_responses_message_item_id;
use crate::formats::shared::model_directives::ReasoningEffort; use crate::formats::shared::model_directives::ReasoningEffort;
use crate::formats::shared::response::remove_empty_pages_from_tool_input_value; use crate::formats::shared::response::remove_empty_pages_from_tool_input_value;
+9 -21
View File
@@ -34,8 +34,8 @@ pub use providers::{
WINDSURF_MODEL_CONFIGS_PATH, WINDSURF_RATE_LIMIT_PATH, WINDSURF_USER_STATUS_PATH, WINDSURF_MODEL_CONFIGS_PATH, WINDSURF_RATE_LIMIT_PATH, WINDSURF_USER_STATUS_PATH,
}; };
pub use quota::{ pub use quota::{
provider_pool_key_account_quota_exhausted, provider_pool_key_quota_hard_blocked, provider_pool_key_account_quota_exhausted, provider_pool_key_model_quota_exhausted,
provider_pool_key_model_quota_exhausted, provider_pool_key_model_quota_hard_blocked, provider_pool_key_model_quota_hard_blocked, provider_pool_key_quota_hard_blocked,
provider_pool_key_scheduling_label, provider_pool_member_quota_snapshot, provider_pool_key_scheduling_label, provider_pool_member_quota_snapshot,
provider_pool_quota_metadata_provider_type, provider_pool_quota_metadata_updated_at, provider_pool_quota_metadata_provider_type, provider_pool_quota_metadata_updated_at,
provider_pool_quota_snapshot_updated_at, provider_pool_quota_snapshot_updated_at,
@@ -1005,14 +1005,12 @@ mod tests {
} }
})); }));
let alpha = service.member_signals( let alpha = service.member_signals("codex", &key, None, Some("vendor-alpha-model"));
"codex",
&key,
None,
Some("vendor-alpha-model"),
);
let beta = service.member_signals("codex", &key, None, Some("vendor-beta-model")); let beta = service.member_signals("codex", &key, None, Some("vendor-beta-model"));
assert!(!alpha.quota_exhausted, "one available alpha window must keep it usable"); assert!(
!alpha.quota_exhausted,
"one available alpha window must keep it usable"
);
assert!(beta.quota_exhausted); assert!(beta.quota_exhausted);
assert!(!beta.quota_hard_blocked); assert!(!beta.quota_hard_blocked);
} }
@@ -1062,12 +1060,7 @@ mod tests {
} }
})); }));
let signals = service.member_signals( let signals = service.member_signals("codex", &key, None, Some("gpt-5.3-codex-spark"));
"codex",
&key,
None,
Some("gpt-5.3-codex-spark"),
);
assert!( assert!(
!signals.quota_exhausted, !signals.quota_exhausted,
"a compact bucket name should match a token in the selected model" "a compact bucket name should match a token in the selected model"
@@ -1093,12 +1086,7 @@ mod tests {
} }
})); }));
let signals = service.member_signals( let signals = service.member_signals("codex", &key, None, Some("gpt-5.4-codex-spark"));
"codex",
&key,
None,
Some("gpt-5.4-codex-spark"),
);
assert!(signals.quota_exhausted); assert!(signals.quota_exhausted);
} }
+2 -2
View File
@@ -7,8 +7,8 @@ use serde_json::{Map, Value};
use crate::capability::{ProviderPoolCapabilities, ProviderPoolCapability}; use crate::capability::{ProviderPoolCapabilities, ProviderPoolCapability};
use crate::plan::{derive_plan_tier, normalize_provider_plan_tier}; use crate::plan::{derive_plan_tier, normalize_provider_plan_tier};
use crate::quota::{ use crate::quota::{
provider_pool_account_blocked, provider_pool_quota_reset_seconds, provider_pool_account_blocked, provider_pool_model_quota_exhausted,
provider_pool_model_quota_exhausted, provider_pool_quota_snapshot_exhausted_decision, provider_pool_quota_reset_seconds, provider_pool_quota_snapshot_exhausted_decision,
provider_pool_quota_usage_ratio, provider_pool_quota_usage_ratio,
}; };
@@ -11,8 +11,8 @@ use crate::provider::{
}; };
use crate::quota::{ use crate::quota::{
provider_pool_current_unix_secs, provider_pool_json_bool, provider_pool_json_f64, provider_pool_current_unix_secs, provider_pool_json_bool, provider_pool_json_f64,
provider_pool_metadata_bucket, provider_pool_quota_snapshot_exhausted_decision, provider_pool_metadata_bucket, provider_pool_model_quota_exhausted,
provider_pool_model_quota_exhausted, provider_pool_reset_deadline_elapsed, provider_pool_quota_snapshot_exhausted_decision, provider_pool_reset_deadline_elapsed,
provider_pool_timestamp_unix_secs, provider_pool_timestamp_unix_secs,
}; };
use crate::quota_refresh::ProviderPoolQuotaRequestSpec; use crate::quota_refresh::ProviderPoolQuotaRequestSpec;
@@ -8,8 +8,8 @@ use crate::provider::{
}; };
use crate::quota::{ use crate::quota::{
provider_pool_current_unix_secs, provider_pool_json_bool, provider_pool_json_f64, provider_pool_current_unix_secs, provider_pool_json_bool, provider_pool_json_f64,
provider_pool_metadata_bucket, provider_pool_quota_snapshot_exhausted_decision, provider_pool_metadata_bucket, provider_pool_model_quota_exhausted,
provider_pool_model_quota_exhausted, provider_pool_reset_deadline_elapsed, provider_pool_quota_snapshot_exhausted_decision, provider_pool_reset_deadline_elapsed,
provider_pool_timestamp_unix_secs, provider_pool_timestamp_unix_secs,
}; };
+49 -23
View File
@@ -106,10 +106,8 @@ pub(crate) fn provider_pool_model_quota_exhausted(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !model_matches.is_empty() { if !model_matches.is_empty() {
let exhausted = provider_pool_explicit_model_windows_exhausted( let exhausted =
model_matches, provider_pool_explicit_model_windows_exhausted(model_matches, observed_at);
observed_at,
);
if resolved.is_none() if resolved.is_none()
|| provider_pool_should_replace_model_quota_resolution( || provider_pool_should_replace_model_quota_resolution(
resolved.as_ref().and_then(|(observed_at, _)| *observed_at), resolved.as_ref().and_then(|(observed_at, _)| *observed_at),
@@ -130,10 +128,7 @@ pub(crate) fn provider_pool_model_quota_exhausted(
.filter(|window| provider_pool_window_family_matches_model(window, &requested)) .filter(|window| provider_pool_window_family_matches_model(window, &requested))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !family_matches.is_empty() { if !family_matches.is_empty() {
let exhausted = provider_pool_any_window_exhausted( let exhausted = provider_pool_any_window_exhausted(family_matches, observed_at);
family_matches,
observed_at,
);
if resolved.is_none() if resolved.is_none()
|| provider_pool_should_replace_model_quota_resolution( || provider_pool_should_replace_model_quota_resolution(
resolved.as_ref().and_then(|(observed_at, _)| *observed_at), resolved.as_ref().and_then(|(observed_at, _)| *observed_at),
@@ -234,7 +229,10 @@ fn provider_pool_window_is_generic(window: &Map<String, Value>) -> bool {
return false; return false;
} }
let code = window.get("code").and_then(Value::as_str).unwrap_or_default(); let code = window
.get("code")
.and_then(Value::as_str)
.unwrap_or_default();
let family = code let family = code
.split_once(['_', ':', '/']) .split_once(['_', ':', '/'])
.map(|(prefix, _)| prefix) .map(|(prefix, _)| prefix)
@@ -248,8 +246,17 @@ fn provider_pool_window_is_generic(window: &Map<String, Value>) -> bool {
.is_some_and(|scope| scope.trim().eq_ignore_ascii_case("account")); .is_some_and(|scope| scope.trim().eq_ignore_ascii_case("account"));
} }
[ [
"weekly", "5h", "daily", "monthly", "primary", "secondary", "account", "quota", "weekly",
"window", "rate", "reset", "5h",
"daily",
"monthly",
"primary",
"secondary",
"account",
"quota",
"window",
"rate",
"reset",
] ]
.contains(&family.as_str()) .contains(&family.as_str())
} }
@@ -301,8 +308,7 @@ fn provider_pool_window_family_matches_model(
.map(|scope| scope.trim().to_ascii_lowercase()); .map(|scope| scope.trim().to_ascii_lowercase());
// A model-scoped window without an explicit model must not accidentally // A model-scoped window without an explicit model must not accidentally
// match a token from its opaque code. // match a token from its opaque code.
if explicit_scope.as_deref() == Some("model") if explicit_scope.as_deref() == Some("model") || provider_pool_window_has_explicit_model(window)
|| provider_pool_window_has_explicit_model(window)
{ {
return false; return false;
} }
@@ -373,9 +379,26 @@ fn provider_pool_is_specific_model_token(token: &str) -> bool {
token.len() >= 4 token.len() >= 4
&& !token.chars().all(|character| character.is_ascii_digit()) && !token.chars().all(|character| character.is_ascii_digit())
&& ![ && ![
"auto", "base", "claude", "codex", "default", "fast", "flash", "free", "auto",
"gemini", "gpt", "latest", "mini", "model", "plus", "pro", "reasoning", "base",
"team", "think", "thinking", "vendor", "claude",
"codex",
"default",
"fast",
"flash",
"free",
"gemini",
"gpt",
"latest",
"mini",
"model",
"plus",
"pro",
"reasoning",
"team",
"think",
"thinking",
"vendor",
] ]
.contains(&token) .contains(&token)
} }
@@ -394,9 +417,9 @@ fn provider_pool_window_has_explicit_model(window: &Map<String, Value>) -> bool
.iter() .iter()
.any(|key| match window.get(*key) { .any(|key| match window.get(*key) {
Some(Value::String(value)) => !value.trim().is_empty(), Some(Value::String(value)) => !value.trim().is_empty(),
Some(Value::Array(values)) => values.iter().any(|value| { Some(Value::Array(values)) => values
value.as_str().is_some_and(|value| !value.trim().is_empty()) .iter()
}), .any(|value| value.as_str().is_some_and(|value| !value.trim().is_empty())),
_ => false, _ => false,
}) })
} }
@@ -610,8 +633,12 @@ fn provider_pool_quota_window_is_exhausted(window: &Map<String, Value>) -> bool
provider_pool_json_bool(window.get("is_exhausted")) provider_pool_json_bool(window.get("is_exhausted"))
.or_else(|| provider_pool_json_bool(window.get("exhausted"))) .or_else(|| provider_pool_json_bool(window.get("exhausted")))
.or_else(|| { .or_else(|| {
provider_pool_json_f64(window.get("used_ratio").or_else(|| window.get("usage_ratio"))) provider_pool_json_f64(
.map(|value| value >= 1.0 - 1e-6) window
.get("used_ratio")
.or_else(|| window.get("usage_ratio")),
)
.map(|value| value >= 1.0 - 1e-6)
}) })
.or_else(|| { .or_else(|| {
provider_pool_json_f64(window.get("used_percent")).map(|value| value >= 100.0 - 1e-6) provider_pool_json_f64(window.get("used_percent")).map(|value| value >= 100.0 - 1e-6)
@@ -625,8 +652,7 @@ fn provider_pool_quota_window_is_exhausted(window: &Map<String, Value>) -> bool
.map(|value| value <= 1e-6) .map(|value| value <= 1e-6)
}) })
.or_else(|| { .or_else(|| {
provider_pool_json_f64(window.get("remaining_percent")) provider_pool_json_f64(window.get("remaining_percent")).map(|value| value <= 1e-6)
.map(|value| value <= 1e-6)
}) })
.or_else(|| { .or_else(|| {
let remaining = provider_pool_json_f64( let remaining = provider_pool_json_f64(