Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
535f759
billing: spool ledger rows the store keeps refusing and replay them
CMGS Oct 17, 2026
026c3fe
budgets: hold the call's estimate against the daily budgets at admission
CMGS Oct 17, 2026
12dafcd
billing: bill an abandoned stream from the upstream's own usage, not …
CMGS Oct 17, 2026
eace497
budgets: reuse QuotaCheck's estimate in BudgetGate
CMGS Oct 17, 2026
e1aac7b
billing: keep the ledger spool from losing or wedging rows
CMGS Oct 17, 2026
4ee2a12
budgets: price the hold only when a day budget applies
CMGS Oct 17, 2026
4a56d6c
billing: settle an abandoned stream before shutdown, bounded and off …
CMGS Oct 17, 2026
cad7ec8
simplify: strip NUL at the client hint, one estimate total, leaner lo…
CMGS Oct 17, 2026
0f3a5c1
cut: fold Settled into BudgetSpend, drop the generation id whitelist
CMGS Oct 17, 2026
9a10d9b
docs: an aborted OpenRouter stream bills a cache write at the prompt …
CMGS Oct 17, 2026
259bffa
perf: hold and settle every day budget in one Redis script
CMGS Oct 17, 2026
9490170
perf: skip the budget scripts when nothing is held
CMGS Oct 17, 2026
ddad28a
perf: cheaper budget scripts, matching main's Redis work per request
CMGS Oct 17, 2026
f71493d
perf: hold and settle day budgets with pipelined INCRBY, not Lua
CMGS Oct 17, 2026
a942589
perf: no budget work when no budget applies, fewer allocations when o…
CMGS Oct 17, 2026
04c343c
budgets: hold the unit price of units known at admission
CMGS Oct 17, 2026
bcdd970
billing: add replayed rows to pruned rollup buckets atomically
CMGS Oct 17, 2026
edc7077
perf: compute usage tokens synchronously, bill at the call site
CMGS Oct 17, 2026
c0146b9
billing: bound the image unit hold, resume a replay where it stopped
CMGS Oct 17, 2026
d93c54a
review: name the replay offset file and row count apart
CMGS Oct 17, 2026
5f6cb2a
billing: serialize replay and retain pruned request ids
CMGS Oct 17, 2026
ecf05c2
Revert billing replay retention and locking
CMGS Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ undocumented_unsafe_blocks = "deny"
[workspace.dependencies]
# --- async runtime ---
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "sync", "net", "signal", "io-util"] }
tokio-util = { version = "0.7", features = ["rt"] }
async-trait = "0.1"
futures = "0.3"
# --- web (server) ---
Expand Down
3 changes: 3 additions & 0 deletions crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,9 @@ pub struct StorageConf {
/// Prune rolled billing records beyond this many; 0 = unlimited.
#[serde(default)]
pub ledger_max_rows: u64,
/// Local file holding ledger rows the store refuses until they replay; empty drops them after the retries.
#[serde(default)]
pub ledger_spool_path: String,
/// Postgres pool size for the store and key-table pools; 0 = their built-in defaults.
#[serde(default)]
pub postgres_max_connections: u32,
Expand Down
22 changes: 21 additions & 1 deletion crates/dag/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::sync::Arc;
use gw_config::GatewayConfig;
use gw_engines::{EngineOutcome, SharedTransport};
use gw_models::{GResult, GatewayError, GatewayRequest, ModelParamV2};
use gw_state::admission::TpmReserve;
use gw_state::admission::{BudgetHold, TpmReserve};
use gw_state::{AkInfo, GatewayState, UserBudget};

pub struct DagContext {
Expand All @@ -28,6 +28,7 @@ pub struct DagContext {
/// A fallback model retries this attempt's upstream fault, so its failure is not the outcome.
pub fallback_ahead: bool,
pub(crate) request_limits_admitted: bool,
pub(crate) reserve_parts: (i64, i64),
/// This request's cache key (computed by cache_lookup, reused by cache_store).
pub cache_key: Option<String>,
/// Governance key for the (AK, model) daily counter; set only when a cap is configured.
Expand All @@ -40,8 +41,11 @@ pub struct DagContext {
pub tpm_reserved: Option<TpmReserve>,
/// The user's budget override resolved at admission, charged at settlement.
pub user_budget: Option<UserBudget>,
/// Day-budget amounts held at admission (same lifecycle as `quota_reserved`).
pub budget_hold: BudgetHold,
/// Outbound DLP buffered this stream; billing waits for the view's delivery result.
pub billing_deferred: bool,
pub served_at: Option<std::time::Instant>,
}

impl DagContext {
Expand All @@ -63,13 +67,16 @@ impl DagContext {
cache_hit: false,
fallback_ahead: false,
request_limits_admitted: false,
reserve_parts: (0, 0),
cache_key: None,
model_quota_key: None,
quota_reserved: None,
quota_at: 0,
tpm_reserved: None,
user_budget: None,
budget_hold: BudgetHold::default(),
billing_deferred: false,
served_at: None,
}
}

Expand All @@ -85,6 +92,19 @@ impl DagContext {
.ok_or_else(|| GatewayError::internal("model param missing after resolve_model"))
}

pub async fn refund_reserves(&mut self) {
let gov = self.state.governance.as_ref();
tokio::join!(
gov.refund_reserves(
&self.ak.ak_id,
self.quota_reserved.take().unwrap_or(0),
self.tpm_reserved.take(),
self.quota_at,
),
std::mem::take(&mut self.budget_hold).refund(gov),
);
}

/// The effective end user: the key's `owner`, else request metadata, else `""`.
pub fn effective_user_id(&self) -> &str {
self.ak
Expand Down
Loading
Loading