mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(openai): add gpt-6-astra billing
Add standard, Flex, Fast, long-context and cache-write rates, and classify Astra as a reasoning model for preview web-search pricing. Cover tier aliases, the context threshold and cost components with backend regression tests. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -202,6 +202,18 @@ struct PricingRule {
|
||||
|
||||
// Source: https://developers.openai.com/api/docs/pricing (verified 2026-08-14).
|
||||
const PRICING_RULES: &[PricingRule] = &[
|
||||
// Astra: https://developers.openai.com/api/docs/models/gpt-6-astra
|
||||
// Rates verified against the pricing table on 2026-09-05.
|
||||
PricingRule {
|
||||
model: "gpt-6-astra",
|
||||
pricing: ModelPricing::new(100_000, 500_000, 10_000)
|
||||
.with_cache_write(125)
|
||||
.with_flex(50_000, 250_000, 5_000)
|
||||
.with_fast(200_000, 1_000_000, 20_000)
|
||||
.with_long(200_000, 750_000, 20_000)
|
||||
.with_long_flex(100_000, 375_000, 10_000)
|
||||
.with_long_fast(400_000, 1_500_000, 40_000),
|
||||
},
|
||||
PricingRule {
|
||||
model: "gpt-5.6-sol",
|
||||
pricing: ModelPricing::new(50_000, 300_000, 5_000)
|
||||
@@ -532,6 +544,7 @@ fn fixed_block_web_search_model(model: &str) -> bool {
|
||||
fn reasoning_model(model: &str) -> bool {
|
||||
let normalized = normalize_model_name(model);
|
||||
normalized.starts_with("gpt-5")
|
||||
|| model_matches_rule(&normalized, "gpt-6-astra")
|
||||
|| normalized.starts_with("o1")
|
||||
|| normalized.starts_with("o3")
|
||||
|| normalized.starts_with("o4")
|
||||
|
||||
@@ -303,6 +303,27 @@ fn decoder_should_use_non_reasoning_preview_web_search_price() {
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn astra_decoder_should_use_reasoning_preview_web_search_price() {
|
||||
let body = concat!(
|
||||
"event: response.created\n",
|
||||
"data: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_preview_search_cost\",\"model\":\"gpt-6-astra\"}}\n\n",
|
||||
"event: response.completed\n",
|
||||
"data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_preview_search_cost\",\"model\":\"gpt-6-astra\",\"status\":\"completed\",\"output\":[{\"type\":\"web_search_call\",\"id\":\"ws_1\",\"status\":\"completed\",\"action\":{\"type\":\"search\"}}],\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"total_tokens\":0}}}\n\n",
|
||||
);
|
||||
let tools = vec![json!({ "type": "web_search_preview" })];
|
||||
let events = CodexCanonicalDecoder::new("fallback")
|
||||
.with_request_tool_pricing("gpt-6-astra", Some(&tools))
|
||||
.push(body.as_bytes())
|
||||
.expect("canonical preview web search response");
|
||||
|
||||
assert!(canonical_facts(&events).into_iter().any(|event| matches!(
|
||||
event,
|
||||
GatewayEvent::CalculatedCost(cost)
|
||||
if cost.total().amount().scaled() == 100_000_000
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoder_should_fail_closed_for_fixed_block_web_search_content() {
|
||||
let body = concat!(
|
||||
|
||||
@@ -30,6 +30,52 @@ fn billing_usage(
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn astra_billing_should_preserve_components_across_tiers_and_context_boundary() {
|
||||
// USD per million tokens, in order: input, cache read, cache write, output.
|
||||
for (tier, input, expected, multiplier) in [
|
||||
(None, 272_000, ["10", "1", "12.5", "50"], 100),
|
||||
(None, 272_001, ["20", "2", "25", "75"], 100),
|
||||
(Some("flex"), 272_000, ["5", "0.5", "6.25", "25"], 50),
|
||||
(Some("flex"), 272_001, ["10", "1", "12.5", "37.5"], 50),
|
||||
(Some("fast"), 272_000, ["20", "2", "25", "100"], 200),
|
||||
(Some("fast"), 272_001, ["40", "4", "50", "150"], 200),
|
||||
(Some("priority"), 272_001, ["40", "4", "50", "150"], 200),
|
||||
] {
|
||||
let breakdown =
|
||||
openai_billing_breakdown("gpt-6-astra", billing_usage(input, 5, 20, 10), tier)
|
||||
.expect("published Astra pricing");
|
||||
let prices = [
|
||||
breakdown.input_price_per_million(),
|
||||
breakdown.cache_read_price_per_million(),
|
||||
breakdown.cache_write_price_per_million(),
|
||||
breakdown.output_price_per_million(),
|
||||
]
|
||||
.map(|price| {
|
||||
price
|
||||
.amount()
|
||||
.to_string()
|
||||
.trim_end_matches('0')
|
||||
.trim_end_matches('.')
|
||||
.to_owned()
|
||||
});
|
||||
assert_eq!(prices, expected, "tier={tier:?}, input={input}");
|
||||
assert_eq!(breakdown.multiplier_percent(), multiplier);
|
||||
}
|
||||
let breakdown = openai_billing_breakdown("gpt-6-astra", billing_usage(100, 5, 20, 10), None)
|
||||
.expect("Astra component pricing");
|
||||
assert_eq!(
|
||||
(
|
||||
breakdown.input_amount().amount().scaled(),
|
||||
breakdown.output_amount().amount().scaled(),
|
||||
breakdown.cache_read_amount().amount().scaled(),
|
||||
breakdown.cache_write_amount().amount().scaled(),
|
||||
breakdown.total_amount().amount().scaled(),
|
||||
),
|
||||
(7_000_000, 2_500_000, 200_000, 1_250_000, 10_950_000),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn billing_breakdown_should_preserve_input_output_and_cache_components() {
|
||||
let breakdown = openai_billing_breakdown("gpt-5.6-sol", billing_usage(100, 5, 20, 10), None)
|
||||
|
||||
Reference in New Issue
Block a user