use std::collections::{HashMap, VecDeque};
use std::future::Future;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::billing::{self, CallCost, CallKind, CallUsage, CostSource, PriceKnown};
use crate::registry::UnifiedRegistry;
use crate::{InferenceError, TokenUsage};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind", content = "id", rename_all = "snake_case")]
pub enum SpendPrincipal {
Agent(String),
Worker(String),
Credential(String),
Operator,
Car(String),
Declarative(String),
}
impl SpendPrincipal {
pub fn key(&self) -> String {
match self {
Self::Agent(id) => format!("agent:{id}"),
Self::Worker(handle) => format!("worker:{handle}"),
Self::Credential(id) => format!("credential:{id}"),
Self::Operator => "operator".to_string(),
Self::Car(purpose) => format!("car:{purpose}"),
Self::Declarative(id) => format!("declarative:{id}"),
}
}
pub const fn kind(&self) -> &'static str {
match self {
Self::Agent(_) => "agent",
Self::Worker(_) => "worker",
Self::Credential(_) => "credential",
Self::Operator => "operator",
Self::Car(_) => "car",
Self::Declarative(_) => "declarative",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum SpendCause {
#[default]
Direct,
Chat,
MemoryEmbedding,
Compaction,
Summary,
Reflection,
HelperAgent,
GoalVerification,
Fallback,
Evolution,
}
impl SpendCause {
pub const fn as_str(self) -> &'static str {
match self {
Self::Direct => "direct",
Self::Chat => "chat",
Self::MemoryEmbedding => "memory_embedding",
Self::Compaction => "compaction",
Self::Summary => "summary",
Self::Reflection => "reflection",
Self::HelperAgent => "helper_agent",
Self::GoalVerification => "goal_verification",
Self::Fallback => "fallback",
Self::Evolution => "evolution",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SpendReceipt {
pub call_id: String,
pub principal: SpendPrincipal,
pub cause: SpendCause,
pub model_used: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cost_usd: Option<f64>,
pub cost_source: CostSource,
pub input_tokens: u64,
pub output_tokens: u64,
#[serde(default)]
pub voided: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trace_id: Option<String>,
}
pub const RECEIPTS_KEPT: usize = 64;
#[derive(Debug, Clone)]
pub struct SpendScope {
pub principal: SpendPrincipal,
pub keys: Vec<String>,
pub cause: SpendCause,
pub run: Option<String>,
pub decision_class: Option<String>,
pub local_only: bool,
receipts: Arc<Mutex<VecDeque<SpendReceipt>>>,
}
impl SpendScope {
pub fn new(principal: SpendPrincipal) -> Self {
let keys = vec![principal.key()];
Self {
principal,
keys,
cause: SpendCause::Direct,
run: None,
decision_class: None,
local_only: false,
receipts: Arc::default(),
}
}
pub fn local_only(mut self) -> Self {
self.local_only = true;
self
}
pub fn with_keys(mut self, keys: impl IntoIterator<Item = String>) -> Self {
for key in keys {
if !self.keys.contains(&key) {
self.keys.push(key);
}
}
self
}
pub fn with_cause(mut self, cause: SpendCause) -> Self {
self.cause = cause;
self
}
pub fn with_run(mut self, run: Option<String>) -> Self {
self.run = run;
self
}
pub fn with_decision_class(mut self, class: Option<String>) -> Self {
if class.is_some() {
self.decision_class = class;
}
self
}
pub fn receipts(&self) -> Vec<SpendReceipt> {
lock(&self.receipts).iter().cloned().collect()
}
pub fn receipt_for_trace(&self, trace_id: &str) -> Option<SpendReceipt> {
lock(&self.receipts)
.iter()
.rev()
.find(|receipt| receipt.trace_id.as_deref() == Some(trace_id))
.cloned()
}
fn push_receipt(&self, receipt: SpendReceipt) {
let mut receipts = lock(&self.receipts);
if receipts.len() >= RECEIPTS_KEPT {
receipts.pop_front();
}
receipts.push_back(receipt);
}
}
fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
tokio::task_local! {
static SCOPE: SpendScope;
static SENT_UNREAD: Arc<std::sync::atomic::AtomicBool>;
}
pub(crate) fn note_sent_unread() {
let _ = SENT_UNREAD.try_with(|sent| sent.store(true, std::sync::atomic::Ordering::Relaxed));
}
#[cfg(test)]
pub(crate) async fn noting_sent_unread<F: Future>(future: F) -> (F::Output, bool) {
let sent = Arc::new(std::sync::atomic::AtomicBool::new(false));
let output = SENT_UNREAD.scope(Arc::clone(&sent), future).await;
(output, sent.load(std::sync::atomic::Ordering::Relaxed))
}
pub(crate) async fn dispatched<F: Future>(ticket: Option<&SpendTicket>, dispatch: F) -> F::Output {
match ticket {
Some(ticket) => {
crate::late_cost::attempt_scope(
ticket.late_attempt,
SENT_UNREAD.scope(Arc::clone(&ticket.sent_unread), dispatch),
)
.await
}
None => dispatch.await,
}
}
pub async fn scope<F: Future>(mut scope: SpendScope, future: F) -> F::Output {
if scope.decision_class.is_none() {
scope.decision_class = current().and_then(|parent| parent.decision_class);
}
SCOPE.scope(scope, future).await
}
pub async fn with_cause<F: Future>(cause: SpendCause, future: F) -> F::Output {
match current() {
Some(scope) => SCOPE.scope(scope.with_cause(cause), future).await,
None => future.await,
}
}
pub async fn with_decision_class<F: Future>(class: Option<String>, future: F) -> F::Output {
match current() {
Some(scope) => SCOPE.scope(scope.with_decision_class(class), future).await,
None => future.await,
}
}
pub fn current() -> Option<SpendScope> {
SCOPE.try_with(|scope| scope.clone()).ok()
}
pub fn receipts() -> Vec<SpendReceipt> {
current().map(|scope| scope.receipts()).unwrap_or_default()
}
pub fn receipt_for_trace(trace_id: &str) -> Option<SpendReceipt> {
current().and_then(|scope| scope.receipt_for_trace(trace_id))
}
pub fn spawn_scoped<F>(future: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
match current() {
Some(scope) => tokio::spawn(SCOPE.scope(scope, future)),
None => tokio::spawn(future),
}
}
pub fn scoped<F: Future>(future: F) -> impl Future<Output = F::Output> {
let scope = current();
async move {
match scope {
Some(scope) => SCOPE.scope(scope, future).await,
None => future.await,
}
}
}
#[derive(Debug, Clone)]
pub struct DispatchFacts {
pub model: String,
pub provider: String,
pub is_local: bool,
pub price: PriceKnown,
pub kind: CallKind,
pub input_estimate: u64,
pub max_output: u64,
pub estimate: CallCost,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpendRefusal {
pub reason: String,
pub message: String,
pub data: Option<serde_json::Value>,
}
pub const PRICE_UNKNOWN: &str = "price_unknown";
pub fn price_unknown_message(model: &str) -> String {
format!("price unknown for {model}; this agent has a spend cap")
}
pub fn cost_unestimable_message(model: &str) -> String {
format!(
"the cost of this call to {model} cannot be estimated before it is sent; this agent has a spend cap"
)
}
pub const UNPRICED_CALL_IN_FLIGHT: &str = "unpriced_call_in_flight";
pub fn unpriced_call_in_flight_message(model: &str) -> String {
format!(
"{model} is priced only when a call finishes, and this agent already has one such call running; this agent has a spend cap, so it may make one at a time"
)
}
pub fn unestimable_refusal(facts: &DispatchFacts) -> Option<SpendRefusal> {
if facts.is_local || facts.estimate.micros.is_some() {
return None;
}
let message = match facts.price {
PriceKnown::ProviderReports => return None,
PriceKnown::ListPrice | PriceKnown::Subscription | PriceKnown::Local => {
cost_unestimable_message(&facts.model)
}
PriceKnown::Unknown => price_unknown_message(&facts.model),
};
Some(SpendRefusal {
reason: PRICE_UNKNOWN.to_string(),
message,
data: Some(serde_json::json!({
"reason": PRICE_UNKNOWN,
"model": facts.model,
})),
})
}
pub fn is_unpriced_refusal(error: &InferenceError) -> bool {
matches!(
error,
InferenceError::SpendRefused { reason, .. }
if reason == PRICE_UNKNOWN || reason == UNPRICED_CALL_IN_FLIGHT
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpendTicketId {
pub principal: String,
pub call_id: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SettledCall {
pub model_used: String,
pub cost: CallCost,
pub input_tokens: u64,
pub output_tokens: u64,
pub voided: bool,
}
pub trait SpendGate: Send + Sync {
fn admit(
&self,
scope: &SpendScope,
facts: &DispatchFacts,
) -> Result<SpendTicketId, SpendRefusal>;
fn capped(&self, scope: &SpendScope) -> bool {
let _ = scope;
false
}
fn requires_price(&self, scope: &SpendScope) -> bool {
self.capped(scope)
}
fn refused(&self, scope: &SpendScope, facts: &DispatchFacts, refusal: &SpendRefusal) {
let _ = (scope, facts, refusal);
}
fn settle(&self, ticket: &SpendTicketId, call: &SettledCall);
fn correct(&self, ticket: &SpendTicketId, cost: CallCost);
}
#[derive(Debug, Default)]
pub struct RecordingGate {
pub admitted: Mutex<Vec<(SpendScope, DispatchFacts)>>,
pub settled: Mutex<Vec<(SpendTicketId, SettledCall)>>,
pub corrected: Mutex<Vec<(SpendTicketId, CallCost)>>,
}
impl SpendGate for RecordingGate {
fn admit(
&self,
scope: &SpendScope,
facts: &DispatchFacts,
) -> Result<SpendTicketId, SpendRefusal> {
let mut admitted = lock(&self.admitted);
let call_id = format!("call-{}", admitted.len() + 1);
admitted.push((scope.clone(), facts.clone()));
Ok(SpendTicketId {
principal: scope.principal.key(),
call_id,
})
}
fn settle(&self, ticket: &SpendTicketId, call: &SettledCall) {
lock(&self.settled).push((ticket.clone(), call.clone()));
}
fn correct(&self, ticket: &SpendTicketId, cost: CallCost) {
lock(&self.corrected).push((ticket.clone(), cost));
}
}
pub struct SpendTicket {
gate: Arc<dyn SpendGate>,
id: SpendTicketId,
scope: SpendScope,
registry: Arc<UnifiedRegistry>,
facts: DispatchFacts,
model_used: String,
usage: Option<TokenUsage>,
speech: Option<CallUsage<'static>>,
trace_id: Option<String>,
voided: bool,
settled: bool,
capped: bool,
cut_off: bool,
ended: bool,
sent_unread: Arc<std::sync::atomic::AtomicBool>,
unpriced_slot: Option<UnpricedSlot>,
late_probe: Option<Arc<crate::LateCostProbe>>,
late_attempt: u64,
}
impl std::fmt::Debug for SpendTicket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SpendTicket")
.field("id", &self.id)
.field("principal", &self.scope.principal)
.field("model", &self.facts.model)
.field("settled", &self.settled)
.finish()
}
}
impl SpendTicket {
pub fn call_id(&self) -> &str {
&self.id.call_id
}
pub fn id(&self) -> &SpendTicketId {
&self.id
}
pub fn scope(&self) -> &SpendScope {
&self.scope
}
pub fn set_trace(&mut self, trace_id: &str) {
self.trace_id = Some(trace_id.to_string());
}
pub fn served(&mut self, model_used: &str, usage: Option<&TokenUsage>) {
self.ended = true;
self.model_used = model_used.to_string();
if let Some(usage) = usage {
self.usage = Some(usage.clone());
}
}
pub fn usage_so_far(&mut self, usage: TokenUsage) {
self.usage = Some(usage);
}
pub fn reported_cost(&mut self, usd: f64) {
let usage = self.usage.get_or_insert_with(TokenUsage::default);
if usage.reported_cost_usd.is_none() {
usage.reported_cost_usd = Some(usd);
}
}
pub fn speech_usage(&mut self, usage: CallUsage<'static>) {
self.speech = Some(usage);
}
pub fn ended_with(&mut self, error: &InferenceError) {
self.ended = true;
if let InferenceError::ContentRefused {
usage: Some(usage), ..
} = error
{
self.usage.get_or_insert_with(|| usage.clone());
}
if error.raised_before_dispatch() {
self.voided = true;
} else if ended_after_dispatch_unread(error)
|| self.sent_unread.load(std::sync::atomic::Ordering::Relaxed)
{
self.cut_off = true;
}
}
pub fn void(&mut self) {
self.ended = true;
self.voided = true;
}
pub fn cut_off(&mut self) {
self.ended = true;
self.cut_off = true;
}
pub fn settle(mut self) -> SpendReceipt {
self.settle_inner()
}
fn settle_inner(&mut self) -> SpendReceipt {
self.settled = true;
let (cost, input_tokens, output_tokens) = if self.voided {
(
CallCost {
micros: None,
source: CostSource::Unknown,
},
0,
0,
)
} else {
self.price()
};
let call = SettledCall {
model_used: self.model_used.clone(),
cost,
input_tokens,
output_tokens,
voided: self.voided,
};
self.gate.settle(&self.id, &call);
self.run_late_lookup(cost.source);
let receipt = SpendReceipt {
call_id: self.id.call_id.clone(),
principal: self.scope.principal.clone(),
cause: self.scope.cause,
model_used: self.model_used.clone(),
cost_usd: cost.micros.map(billing::micros_to_usd),
cost_source: cost.source,
input_tokens,
output_tokens,
voided: self.voided,
trace_id: self.trace_id.clone(),
};
self.scope.push_receipt(receipt.clone());
receipt
}
fn run_late_lookup(&mut self, source: CostSource) {
let late = if self.voided {
None
} else {
self.late_probe
.as_ref()
.and_then(|probe| probe.take_for_attempt(self.late_attempt, &self.model_used))
.filter(|_| !matches!(source, CostSource::Provider | CostSource::Subscription))
};
let slot = self.unpriced_slot.take();
let (Some(late), Ok(runtime)) = (late, tokio::runtime::Handle::try_current()) else {
drop(slot);
return;
};
let release = slot.and_then(|slot| slot.await_late_figure(&self.id.call_id));
let ceiling =
billing::provider_figure_ceiling(&self.registry, &self.model_used, self.usage.as_ref());
let gate = Arc::clone(&self.gate);
let id = self.id.clone();
let model = self.model_used.clone();
runtime.spawn(async move {
let figure = late
.resolve()
.await
.and_then(|usd| billing::accept_provider_figure(&model, usd, ceiling));
if let Some(usd) = figure {
gate.correct(
&id,
CallCost {
micros: Some(billing::usd_to_micros_up(usd)),
source: CostSource::Provider,
},
);
}
if let Some(slots) = release {
slots.late_figure_resolved(&id.call_id);
}
});
}
fn price(&self) -> (CallCost, u64, u64) {
if let Some(speech) = self.speech {
let cost = billing::call_cost(&self.registry, &self.model_used, speech);
let (input, output) = match speech {
CallUsage::AudioSeconds(seconds) => (seconds.ceil().max(0.0) as u64, 0),
CallUsage::Characters(chars) => (chars, 0),
CallUsage::Tokens(_) => (0, 0),
};
return (cost, input, output);
}
let provider_figure = self
.usage
.as_ref()
.is_some_and(|usage| usage.reported_cost_usd.is_some());
if self.cut_off && self.capped && self.facts.kind == CallKind::Tokens && !provider_figure {
let mut usage = self.usage.clone().unwrap_or_default();
if usage.prompt_tokens == 0
&& usage.cache_read_input_tokens == 0
&& usage.cache_creation_input_tokens == 0
{
usage.prompt_tokens = self.facts.input_estimate;
}
usage.completion_tokens = usage.completion_tokens.max(self.facts.max_output);
usage.total_tokens = usage.prompt_tokens.saturating_add(usage.completion_tokens);
let input = usage
.prompt_tokens
.saturating_add(usage.cache_read_input_tokens)
.saturating_add(usage.cache_creation_input_tokens);
return (
billing::call_cost(
&self.registry,
&self.model_used,
CallUsage::Tokens(Some(&usage)),
),
input,
usage.completion_tokens,
);
}
match (&self.usage, self.facts.kind) {
(Some(usage), CallKind::Tokens) => (
billing::call_cost(
&self.registry,
&self.model_used,
CallUsage::Tokens(Some(usage)),
),
usage
.prompt_tokens
.saturating_add(usage.cache_read_input_tokens)
.saturating_add(usage.cache_creation_input_tokens),
usage.completion_tokens,
),
_ => (
estimate_cost(
&self.registry,
&self.model_used,
self.facts.kind,
self.facts.input_estimate,
0,
),
self.facts.input_estimate,
0,
),
}
}
}
impl Drop for SpendTicket {
fn drop(&mut self) {
if !self.settled {
if !self.ended {
self.cut_off = true;
}
self.settle_inner();
}
}
}
fn ended_after_dispatch_unread(error: &InferenceError) -> bool {
match error {
InferenceError::DeadlineExceeded { .. } | InferenceError::ControlledTermination => true,
InferenceError::Transient {
status: Some(status),
..
} => (200..300).contains(status),
_ => false,
}
}
pub fn void_pre_dispatch(
ticket: &mut Option<SpendTicket>,
error: InferenceError,
) -> InferenceError {
if let Some(ticket) = ticket.as_mut() {
ticket.void();
}
error
}
pub fn estimate_cost(
registry: &UnifiedRegistry,
model: &str,
kind: CallKind,
input_estimate: u64,
max_output: u64,
) -> CallCost {
let usage = match kind {
CallKind::Tokens => {
let usage = TokenUsage {
prompt_tokens: input_estimate,
completion_tokens: max_output,
total_tokens: input_estimate.saturating_add(max_output),
..Default::default()
};
return billing::call_cost(registry, model, CallUsage::Tokens(Some(&usage)));
}
CallKind::AudioSeconds => CallUsage::AudioSeconds(input_estimate as f64),
CallKind::Characters => CallUsage::Characters(input_estimate),
};
billing::call_cost(registry, model, usage)
}
#[derive(Clone)]
pub(crate) struct InstalledGate {
pub gate: Arc<dyn SpendGate>,
pub require_scope: bool,
pub local_only: bool,
pub unpriced: Arc<UnpricedSlots>,
}
pub const UNPRICED_LATE_FIGURE_HOLD: Duration =
Duration::from_secs(crate::late_cost::LATE_COST_WINDOW.as_secs() + 60);
#[derive(Debug, Default)]
pub struct UnpricedSlots {
held: Mutex<HashMap<String, HeldSlot>>,
next: std::sync::atomic::AtomicU64,
}
#[derive(Debug)]
struct HeldSlot {
id: u64,
awaiting: Option<(String, tokio::time::Instant)>,
}
impl HeldSlot {
fn lapsed(&self) -> bool {
self.awaiting
.as_ref()
.is_some_and(|(_, until)| tokio::time::Instant::now() >= *until)
}
}
impl UnpricedSlots {
fn take(self: &Arc<Self>, principal: &str) -> Option<UnpricedSlot> {
let id = self.next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let mut held = lock(&self.held);
if held.get(principal).is_some_and(|slot| !slot.lapsed()) {
return None;
}
held.insert(principal.to_string(), HeldSlot { id, awaiting: None });
Some(UnpricedSlot {
slots: Arc::clone(self),
principal: principal.to_string(),
id,
armed: true,
})
}
pub fn held(&self, principal: &str) -> bool {
lock(&self.held)
.get(principal)
.is_some_and(|slot| !slot.lapsed())
}
pub fn late_figure_resolved(&self, call_id: &str) {
lock(&self.held).retain(|_, slot| {
slot.awaiting
.as_ref()
.is_none_or(|(waiting_for, _)| waiting_for != call_id)
});
}
}
struct UnpricedSlot {
slots: Arc<UnpricedSlots>,
principal: String,
id: u64,
armed: bool,
}
impl UnpricedSlot {
fn await_late_figure(mut self, call_id: &str) -> Option<Arc<UnpricedSlots>> {
let mut held = lock(&self.slots.held);
let slot = held.get_mut(&self.principal)?;
if slot.id != self.id {
return None;
}
slot.awaiting = Some((
call_id.to_string(),
tokio::time::Instant::now() + UNPRICED_LATE_FIGURE_HOLD,
));
self.armed = false;
drop(held);
Some(Arc::clone(&self.slots))
}
}
impl Drop for UnpricedSlot {
fn drop(&mut self) {
if !self.armed {
return;
}
let mut held = lock(&self.slots.held);
if held
.get(&self.principal)
.is_some_and(|slot| slot.id == self.id)
{
held.remove(&self.principal);
}
}
}
pub(crate) struct UnrecordedRefusal {
gate: Arc<dyn SpendGate>,
scope: SpendScope,
facts: DispatchFacts,
refusal: SpendRefusal,
}
impl UnrecordedRefusal {
pub(crate) fn error(&self) -> InferenceError {
InferenceError::SpendRefused {
reason: self.refusal.reason.clone(),
message: self.refusal.message.clone(),
data: self.refusal.data.clone(),
}
}
pub(crate) fn reason(&self) -> &str {
&self.refusal.reason
}
pub(crate) fn is(&self, error: &InferenceError) -> bool {
matches!(
error,
InferenceError::SpendRefused { reason, message, .. }
if *reason == self.refusal.reason && *message == self.refusal.message
)
}
pub(crate) fn record(self) -> InferenceError {
self.gate.refused(&self.scope, &self.facts, &self.refusal);
self.error()
}
}
pub(crate) enum CandidateRefusal {
Final(InferenceError),
Unrecorded(UnrecordedRefusal),
}
impl CandidateRefusal {
pub(crate) fn recorded(self) -> InferenceError {
match self {
Self::Final(error) => error,
Self::Unrecorded(refusal) => refusal.record(),
}
}
}
pub const NO_SCOPE_MESSAGE: &str = "model call has no spend scope (CAR bug)";
pub const REMOTE_IN_LOCAL_WORKER: &str = "remote_in_local_worker";
pub const REMOTE_IN_LOCAL_ONLY_SCOPE: &str = "remote_in_local_only_scope";
pub const REMOTE_IN_LOCAL_ONLY_SCOPE_MESSAGE: &str = "this work runs with no one watching, so it may use only local models; a remote model would spend money unattended";
pub fn refuse_remote_in_local_only_scope(
schema: Option<&crate::schema::ModelSchema>,
) -> Result<(), InferenceError> {
if current().is_some_and(|scope| scope.local_only)
&& !schema.is_some_and(crate::schema::ModelSchema::is_local)
{
return Err(InferenceError::SpendRefused {
reason: REMOTE_IN_LOCAL_ONLY_SCOPE.to_string(),
message: REMOTE_IN_LOCAL_ONLY_SCOPE_MESSAGE.to_string(),
data: None,
});
}
Ok(())
}
pub const REMOTE_IN_LOCAL_WORKER_MESSAGE: &str =
"remote model dispatched inside the on-device inference worker (CAR bug)";
#[derive(Debug, Default)]
pub struct LocalOnlyGate;
impl SpendGate for LocalOnlyGate {
fn admit(
&self,
scope: &SpendScope,
facts: &DispatchFacts,
) -> Result<SpendTicketId, SpendRefusal> {
debug_assert!(facts.is_local, "the local-only check runs before the gate");
Ok(SpendTicketId {
principal: scope.principal.key(),
call_id: "local".to_string(),
})
}
fn settle(&self, _ticket: &SpendTicketId, _call: &SettledCall) {}
fn correct(&self, _ticket: &SpendTicketId, _cost: CallCost) {}
}
impl InstalledGate {
pub(crate) fn admit(
&self,
registry: &Arc<UnifiedRegistry>,
facts: DispatchFacts,
attempt: usize,
) -> Result<Option<SpendTicket>, InferenceError> {
self.admit_candidate(registry, facts, attempt)
.map_err(CandidateRefusal::recorded)
}
pub(crate) fn admit_candidate(
&self,
registry: &Arc<UnifiedRegistry>,
facts: DispatchFacts,
attempt: usize,
) -> Result<Option<SpendTicket>, CandidateRefusal> {
if self.local_only && !facts.is_local {
return Err(CandidateRefusal::Final(InferenceError::SpendRefused {
reason: REMOTE_IN_LOCAL_WORKER.to_string(),
message: REMOTE_IN_LOCAL_WORKER_MESSAGE.to_string(),
data: None,
}));
}
if !facts.is_local && current().is_some_and(|scope| scope.local_only) {
return Err(CandidateRefusal::Final(InferenceError::SpendRefused {
reason: REMOTE_IN_LOCAL_ONLY_SCOPE.to_string(),
message: REMOTE_IN_LOCAL_ONLY_SCOPE_MESSAGE.to_string(),
data: None,
}));
}
let Some(scope) = current() else {
if self.require_scope && !facts.is_local {
return Err(CandidateRefusal::Final(InferenceError::SpendRefused {
reason: "no_scope".to_string(),
message: NO_SCOPE_MESSAGE.to_string(),
data: None,
}));
}
return Ok(None);
};
let capped = !facts.is_local && self.gate.capped(&scope);
let unestimable = capped && facts.estimate.micros.is_none();
let mut unpriced_slot = None;
if unestimable {
if let Some(refusal) =
unestimable_refusal(&facts).filter(|_| self.gate.requires_price(&scope))
{
return Err(CandidateRefusal::Unrecorded(UnrecordedRefusal {
gate: Arc::clone(&self.gate),
scope,
facts,
refusal,
}));
}
}
if unestimable && matches!(facts.price, PriceKnown::ProviderReports) {
match self.unpriced.take(&scope.principal.key()) {
Some(slot) => unpriced_slot = Some(slot),
None => {
let refusal = SpendRefusal {
reason: UNPRICED_CALL_IN_FLIGHT.to_string(),
message: unpriced_call_in_flight_message(&facts.model),
data: Some(serde_json::json!({
"reason": UNPRICED_CALL_IN_FLIGHT,
"model": facts.model,
})),
};
return Err(CandidateRefusal::Unrecorded(UnrecordedRefusal {
gate: Arc::clone(&self.gate),
scope,
facts,
refusal,
}));
}
}
}
let scope = if attempt > 0 {
scope.with_cause(SpendCause::Fallback)
} else {
scope
};
let id = self.gate.admit(&scope, &facts).map_err(|refusal| {
CandidateRefusal::Final(InferenceError::SpendRefused {
reason: refusal.reason,
message: refusal.message,
data: refusal.data,
})
})?;
Ok(Some(SpendTicket {
gate: Arc::clone(&self.gate),
id,
scope,
registry: Arc::clone(registry),
model_used: facts.model.clone(),
facts,
usage: None,
speech: None,
trace_id: None,
voided: false,
settled: false,
capped,
cut_off: false,
ended: false,
sent_unread: Default::default(),
late_probe: crate::late_cost::current_probe(),
late_attempt: crate::late_cost::next_attempt(),
unpriced_slot,
}))
}
}
pub fn facts_for(
registry: &UnifiedRegistry,
schema: Option<&crate::schema::ModelSchema>,
model: &str,
kind: CallKind,
input_estimate: u64,
max_output: u64,
) -> DispatchFacts {
let (provider, is_local, price) = match schema {
Some(schema) => (
schema.provider.clone(),
schema.is_local(),
billing::price_known(schema, kind),
),
None => (String::new(), false, PriceKnown::Unknown),
};
let model = schema.map_or_else(|| model.to_string(), |schema| schema.id.clone());
let estimate = estimate_cost(registry, &model, kind, input_estimate, max_output);
DispatchFacts {
model,
provider,
is_local,
price,
kind,
input_estimate,
max_output,
estimate,
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
fn registry_with(model: &str, per_token: f64) -> Arc<UnifiedRegistry> {
let mut registry = UnifiedRegistry::new_empty(std::env::temp_dir());
registry.register(crate::ModelSchema {
id: model.into(),
name: model.into(),
provider: "test".into(),
family: "test".into(),
version: "1".into(),
capabilities: vec![crate::ModelCapability::Generate],
context_length: 8_192,
max_output_tokens: Some(1_024),
param_count: String::new(),
quantization: None,
performance: Default::default(),
cost: crate::schema::CostModel {
input_per_mtok: Some(per_token * 1_000_000.0),
output_per_mtok: Some(per_token * 1_000_000.0),
..Default::default()
},
source: crate::ModelSource::Delegated { hint: None },
tags: vec![],
supported_params: vec![],
public_benchmarks: vec![],
trust_tier: crate::TrustTier::Community,
deprecated: false,
available: true,
weights_ready: true,
});
Arc::new(registry)
}
fn installed(gate: &Arc<RecordingGate>, require_scope: bool) -> InstalledGate {
InstalledGate {
gate: gate.clone() as Arc<dyn SpendGate>,
require_scope,
local_only: false,
unpriced: Default::default(),
}
}
fn local_only() -> InstalledGate {
InstalledGate {
gate: Arc::new(LocalOnlyGate),
require_scope: false,
local_only: true,
unpriced: Default::default(),
}
}
fn local_registry() -> Arc<UnifiedRegistry> {
let mut registry = UnifiedRegistry::new_empty(std::env::temp_dir());
registry.register(crate::ModelSchema {
id: "local/m".into(),
name: "local/m".into(),
provider: "local".into(),
family: "test".into(),
version: "1".into(),
capabilities: vec![crate::ModelCapability::Generate],
context_length: 8_192,
max_output_tokens: Some(1_024),
param_count: String::new(),
quantization: None,
performance: Default::default(),
cost: Default::default(),
source: crate::ModelSource::Mlx {
hf_repo: "x".into(),
hf_weight_file: None,
},
tags: vec![],
supported_params: vec![],
public_benchmarks: vec![],
trust_tier: crate::TrustTier::Community,
deprecated: false,
available: true,
weights_ready: true,
});
Arc::new(registry)
}
fn facts(registry: &UnifiedRegistry, model: &str) -> DispatchFacts {
let schema = registry.get(model).cloned();
facts_for(registry, schema.as_ref(), model, CallKind::Tokens, 10, 20)
}
fn provider_reports_registry() -> Arc<UnifiedRegistry> {
let mut registry = UnifiedRegistry::new_empty(std::env::temp_dir());
registry.register(crate::ModelSchema {
id: "openrouter/unlisted".into(),
name: "openrouter/unlisted".into(),
provider: "openrouter".into(),
family: "test".into(),
version: "1".into(),
capabilities: vec![crate::ModelCapability::Generate],
context_length: 8_192,
max_output_tokens: Some(1_024),
param_count: String::new(),
quantization: None,
performance: Default::default(),
cost: Default::default(),
source: crate::ModelSource::RemoteApi {
endpoint: "https://openrouter.ai/api/v1".into(),
api_key_env: "OPENROUTER_API_KEY".into(),
api_key_envs: vec![],
api_version: None,
protocol: crate::schema::ApiProtocol::OpenRouter,
},
tags: vec![],
supported_params: vec![],
public_benchmarks: vec![],
trust_tier: crate::TrustTier::Community,
deprecated: false,
available: true,
weights_ready: true,
});
Arc::new(registry)
}
#[derive(Default)]
pub(crate) struct CappedGate {
pub(crate) inner: RecordingGate,
pub(crate) refused: Mutex<Vec<(DispatchFacts, SpendRefusal)>>,
}
impl SpendGate for CappedGate {
fn admit(
&self,
scope: &SpendScope,
facts: &DispatchFacts,
) -> Result<SpendTicketId, SpendRefusal> {
self.inner.admit(scope, facts)
}
fn capped(&self, _scope: &SpendScope) -> bool {
true
}
fn refused(&self, _scope: &SpendScope, facts: &DispatchFacts, refusal: &SpendRefusal) {
lock(&self.refused).push((facts.clone(), refusal.clone()));
}
fn settle(&self, ticket: &SpendTicketId, call: &SettledCall) {
self.inner.settle(ticket, call);
}
fn correct(&self, ticket: &SpendTicketId, cost: CallCost) {
self.inner.correct(ticket, cost);
}
}
fn capped_installed(capped: &Arc<CappedGate>) -> InstalledGate {
InstalledGate {
gate: capped.clone() as Arc<dyn SpendGate>,
require_scope: true,
local_only: false,
unpriced: Default::default(),
}
}
fn bot() -> SpendScope {
SpendScope::new(SpendPrincipal::Agent("bot".into()))
}
#[tokio::test]
async fn a_capped_principal_is_refused_a_model_with_no_price_at_all() {
let registry = provider_reports_registry();
let mut facts = facts(®istry, "openrouter/unlisted");
facts.price = PriceKnown::Unknown;
assert_eq!(facts.estimate.micros, None);
assert!(!facts.is_local);
let capped = Arc::new(CappedGate::default());
let error = scope(bot(), async {
capped_installed(&capped)
.admit(®istry, facts.clone(), 0)
.expect_err("refused before anything is sent")
})
.await;
assert!(matches!(
error,
InferenceError::SpendRefused { ref reason, ref message, .. }
if reason == PRICE_UNKNOWN
&& message == "price unknown for openrouter/unlisted; this agent has a spend cap"
));
assert!(error.raised_before_dispatch());
assert!(
capped.inner.admitted.lock().unwrap().is_empty(),
"admit was never consulted"
);
{
let refused = capped.refused.lock().unwrap();
assert_eq!(refused.len(), 1);
assert_eq!(refused[0].1.reason, PRICE_UNKNOWN);
}
let uncapped = Arc::new(RecordingGate::default());
let ticket = scope(
SpendScope::new(SpendPrincipal::Agent("free".into())),
async {
installed(&uncapped, true)
.admit(®istry, facts, 0)
.expect("an uncapped principal is never refused")
},
)
.await;
assert!(ticket.is_some());
let admitted = uncapped.admitted.lock().unwrap();
assert_eq!(admitted.len(), 1);
assert_eq!(admitted[0].1.estimate.micros, None);
}
#[tokio::test]
async fn a_capped_principal_makes_provider_priced_calls_one_at_a_time() {
let registry = provider_reports_registry();
let facts = facts(®istry, "openrouter/unlisted");
assert_eq!(facts.price, PriceKnown::ProviderReports);
assert_eq!(facts.estimate.micros, None);
let capped = Arc::new(CappedGate::default());
let gate = capped_installed(&capped);
let (receipt_reported, receipt_silent) = scope(bot(), async {
let mut first = gate
.admit(®istry, facts.clone(), 0)
.expect("the first unpriced call is admitted")
.expect("metered");
assert!(gate.unpriced.held("agent:bot"));
let second = gate
.admit(®istry, facts.clone(), 0)
.expect_err("a second while the first runs is refused");
assert!(matches!(
second,
InferenceError::SpendRefused { ref reason, ref message, .. }
if reason == UNPRICED_CALL_IN_FLIGHT
&& message == &unpriced_call_in_flight_message("openrouter/unlisted")
));
assert!(second.raised_before_dispatch());
let priced_registry = registry_with("test/m", 0.000_001);
gate.admit(
&priced_registry,
super::tests::facts(&priced_registry, "test/m"),
0,
)
.expect("a priced call is admitted")
.expect("metered");
first.served(
"openrouter/unlisted",
Some(&TokenUsage {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
reported_cost_usd: Some(0.42),
..Default::default()
}),
);
let reported = first.settle();
assert!(!gate.unpriced.held("agent:bot"), "freed at settle");
let mut silent = gate
.admit(®istry, facts.clone(), 0)
.expect("admitted once the first settled")
.expect("metered");
silent.served(
"openrouter/unlisted",
Some(&TokenUsage {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
..Default::default()
}),
);
(reported, silent.settle())
})
.await;
assert_eq!(receipt_reported.cost_usd, Some(0.42));
assert_eq!(receipt_reported.cost_source, CostSource::Provider);
assert_eq!(receipt_silent.cost_usd, None);
assert_eq!(receipt_silent.cost_source, CostSource::Unknown);
scope(
SpendScope::new(SpendPrincipal::Agent("other".into())),
async {
gate.admit(®istry, facts.clone(), 0)
.expect("another principal is not held up")
},
)
.await;
let refused = capped.refused.lock().unwrap();
assert_eq!(refused.len(), 1, "the refusal the caller received");
assert_eq!(refused[0].1.reason, UNPRICED_CALL_IN_FLIGHT);
let settled = capped.inner.settled.lock().unwrap();
assert_eq!(settled[1].1.cost.micros, Some(420_000));
assert_eq!(settled[1].1.cost.source, CostSource::Provider);
}
fn lookup_at(base: &str, generation: &str) -> crate::LateCost {
crate::LateCost::openrouter(
crate::tls_client::test_seam::trust_store_independent_client(),
base,
generation,
Vec::new(),
"openrouter/unlisted",
"openrouter/unlisted",
)
.expect("a generation id")
}
async fn eventually(what: &str, mut done: impl FnMut() -> bool) {
let deadline = std::time::Instant::now() + crate::LOADED_WAIT;
while !done() {
assert!(
std::time::Instant::now() < deadline,
"timed out waiting for {what}"
);
tokio::time::sleep(Duration::from_millis(20)).await;
}
}
#[tokio::test]
async fn a_provider_priced_call_holds_its_slot_until_its_late_figure_lands() {
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
let provider = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/v1/generation"))
.and(query_param("id", "gen-1"))
.respond_with(
ResponseTemplate::new(200)
.set_body_json(serde_json::json!({"data": {"total_cost": 0.42}}))
.set_delay(Duration::from_millis(500)),
)
.mount(&provider)
.await;
let registry = provider_reports_registry();
let facts = facts(®istry, "openrouter/unlisted");
let capped = Arc::new(CappedGate::default());
let gate = capped_installed(&capped);
let probe = Arc::new(crate::LateCostProbe::default());
crate::scope_late_cost(
probe.clone(),
scope(bot(), async {
let mut first = gate
.admit(®istry, facts.clone(), 0)
.expect("admitted")
.expect("metered");
probe.set(lookup_at(&provider.uri(), "gen-1"));
first.served("openrouter/unlisted", None);
let receipt = first.settle();
assert_eq!(receipt.cost_source, CostSource::Unknown);
let held = gate
.admit(®istry, facts.clone(), 0)
.expect_err("the slot is held until the late figure lands");
assert!(matches!(
held,
InferenceError::SpendRefused { ref reason, .. }
if reason == UNPRICED_CALL_IN_FLIGHT
));
eventually("the late figure", || {
!capped.inner.corrected.lock().unwrap().is_empty()
})
.await;
eventually("the slot freed", || !gate.unpriced.held("agent:bot")).await;
gate.admit(®istry, facts.clone(), 0)
.expect("admitted once the late figure landed")
.expect("metered");
}),
)
.await;
let corrected = capped.inner.corrected.lock().unwrap();
assert_eq!(corrected.len(), 1);
assert_eq!(corrected[0].0.call_id, "call-1", "the call it was for");
assert_eq!(corrected[0].1.micros, Some(420_000));
assert_eq!(corrected[0].1.source, CostSource::Provider);
}
#[tokio::test(start_paused = true)]
async fn a_late_figure_that_never_comes_holds_the_slot_for_a_bounded_time() {
let slots = Arc::new(UnpricedSlots::default());
let slot = slots.take("agent:bot").expect("free");
let _release = slot
.await_late_figure("call-1")
.expect("the call still held the slot");
tokio::time::advance(UNPRICED_LATE_FIGURE_HOLD - Duration::from_secs(1)).await;
assert!(slots.held("agent:bot"), "still within the hold");
tokio::time::advance(Duration::from_secs(2)).await;
assert!(!slots.held("agent:bot"), "the hold ended");
let registry = provider_reports_registry();
let capped = Arc::new(CappedGate::default());
let gate = capped_installed(&capped);
let probe = Arc::new(crate::LateCostProbe::default());
crate::scope_late_cost(
probe,
scope(bot(), async {
let mut call = gate
.admit(®istry, facts(®istry, "openrouter/unlisted"), 0)
.expect("admitted")
.expect("metered");
call.served("openrouter/unlisted", None);
call.settle();
assert!(!gate.unpriced.held("agent:bot"), "nothing to wait for");
}),
)
.await;
}
#[tokio::test]
async fn a_capped_call_that_ended_unread_is_charged_its_estimate() {
let registry = registry_with("test/m", 1.0);
let cases = [
(None, 30_000_000, "dropped mid-call"),
(
Some(InferenceError::DeadlineExceeded {
applied_ms: 1_000,
elapsed_ms: 1_000,
last_error: String::new(),
}),
30_000_000,
"the caller's deadline elapsed",
),
(
Some(InferenceError::ControlledTermination),
30_000_000,
"the run was cancelled",
),
(
Some(InferenceError::Transient {
status: Some(200),
message: String::new(),
}),
30_000_000,
"a 200 whose body could not be read",
),
(
Some(InferenceError::Transient {
status: None,
message: String::new(),
}),
10_000_000,
"a failure with no status",
),
(
Some(InferenceError::InferenceFailed(String::new())),
10_000_000,
"a provider error",
),
];
for (error, expected, case) in cases {
let capped = Arc::new(CappedGate::default());
let gate = capped_installed(&capped);
scope(bot(), async {
let mut ticket = gate
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap()
.expect("a ticket");
if let Some(error) = &error {
ticket.ended_with(error);
}
drop(ticket);
})
.await;
let settled = capped.inner.settled.lock().unwrap();
assert_eq!(settled[0].1.cost.micros, Some(expected), "{case}");
}
}
#[tokio::test]
async fn a_refused_admission_frees_the_slot_it_took() {
struct AtCap;
impl SpendGate for AtCap {
fn admit(
&self,
_scope: &SpendScope,
_facts: &DispatchFacts,
) -> Result<SpendTicketId, SpendRefusal> {
Err(SpendRefusal {
reason: "budget_reached".into(),
message: "budget reached until 3:00 PM EDT".into(),
data: None,
})
}
fn capped(&self, _scope: &SpendScope) -> bool {
true
}
fn settle(&self, _ticket: &SpendTicketId, _call: &SettledCall) {}
fn correct(&self, _ticket: &SpendTicketId, _cost: CallCost) {}
}
let registry = provider_reports_registry();
let gate = InstalledGate {
gate: Arc::new(AtCap),
require_scope: true,
local_only: false,
unpriced: Default::default(),
};
scope(bot(), async {
let error = gate
.admit(®istry, facts(®istry, "openrouter/unlisted"), 0)
.expect_err("at the cap");
assert!(matches!(
error,
InferenceError::SpendRefused { ref reason, .. } if reason == "budget_reached"
));
assert!(!gate.unpriced.held("agent:bot"));
})
.await;
}
#[tokio::test]
async fn a_listed_model_with_no_estimate_is_refused_with_accurate_text() {
let registry = registry_with("test/m", 0.000_001);
let schema = registry.get("test/m").cloned();
let empty = Arc::new(UnifiedRegistry::new_empty(std::env::temp_dir()));
let facts = facts_for(&empty, schema.as_ref(), "test/m", CallKind::Tokens, 10, 20);
assert_eq!(facts.price, PriceKnown::ListPrice);
assert_eq!(facts.estimate.micros, None);
let capped = Arc::new(CappedGate::default());
let error = scope(bot(), async {
capped_installed(&capped)
.admit(®istry, facts, 0)
.expect_err("no estimate to hold in flight")
})
.await;
let InferenceError::SpendRefused {
reason, message, ..
} = error
else {
panic!("a spend refusal");
};
assert_eq!(reason, PRICE_UNKNOWN);
assert_eq!(message, cost_unestimable_message("test/m"));
assert!(!message.contains("price unknown"), "{message}");
}
#[tokio::test]
async fn an_unpriced_candidate_is_refused_unrecorded_until_the_caller_receives_it() {
let registry = provider_reports_registry();
let mut facts = facts(®istry, "openrouter/unlisted");
facts.price = PriceKnown::Unknown;
let capped = Arc::new(CappedGate::default());
let gate = capped_installed(&capped);
let refusal = scope(bot(), async {
match gate.admit_candidate(®istry, facts, 0) {
Err(CandidateRefusal::Unrecorded(refusal)) => refusal,
Err(CandidateRefusal::Final(error)) => panic!("final: {error}"),
Ok(_) => panic!("admitted"),
}
})
.await;
assert!(
capped.refused.lock().unwrap().is_empty(),
"not recorded yet"
);
assert!(refusal.is(&refusal.error()));
let error = refusal.record();
assert!(matches!(
error,
InferenceError::SpendRefused { ref reason, .. } if reason == PRICE_UNKNOWN
));
assert_eq!(capped.refused.lock().unwrap().len(), 1);
}
#[tokio::test]
async fn unscoped_remote_dispatch_is_refused_when_a_scope_is_required() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let error = installed(&gate, true)
.admit(®istry, facts(®istry, "test/m"), 0)
.expect_err("refused");
assert!(matches!(
error,
InferenceError::SpendRefused { ref reason, ref message, .. }
if reason == "no_scope" && message == NO_SCOPE_MESSAGE
));
assert!(error.raised_before_dispatch());
assert!(gate.admitted.lock().unwrap().is_empty());
}
#[tokio::test]
async fn unscoped_dispatch_runs_unmetered_when_no_scope_is_required() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let ticket = installed(&gate, false)
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap();
assert!(ticket.is_none());
}
#[tokio::test]
async fn unscoped_local_dispatch_is_not_refused() {
let registry = local_registry();
let gate = Arc::new(RecordingGate::default());
let facts = facts(®istry, "local/m");
assert!(facts.is_local);
assert!(installed(&gate, true)
.admit(®istry, facts, 0)
.unwrap()
.is_none());
}
#[tokio::test]
async fn a_local_only_gate_refuses_a_remote_dispatch_with_or_without_a_scope() {
let registry = registry_with("test/m", 1.0);
let remote = facts(®istry, "test/m");
assert!(!remote.is_local);
let unscoped = local_only()
.admit(®istry, remote.clone(), 0)
.expect_err("refused");
assert!(matches!(
unscoped,
InferenceError::SpendRefused { ref reason, ref message, .. }
if reason == REMOTE_IN_LOCAL_WORKER && message == REMOTE_IN_LOCAL_WORKER_MESSAGE
));
assert!(unscoped.raised_before_dispatch());
let (scoped, receipts) = scope(
SpendScope::new(SpendPrincipal::Agent("bot".into())),
async {
let error = local_only()
.admit(®istry, remote, 1)
.expect_err("refused under a scope too");
(error, receipts())
},
)
.await;
assert!(matches!(
scoped,
InferenceError::SpendRefused { ref reason, .. } if reason == REMOTE_IN_LOCAL_WORKER
));
assert!(
receipts.is_empty(),
"nothing was admitted, so nothing settled"
);
}
#[tokio::test]
async fn a_local_only_scope_refuses_remote_and_admits_local_dispatch() {
let remote_registry = registry_with("test/m", 1.0);
let local_registry = local_registry();
let gate = Arc::new(RecordingGate::default());
let unattended = SpendScope::new(SpendPrincipal::Car("heal".into())).local_only();
let refused = scope(unattended.clone(), async {
installed(&gate, true)
.admit(&remote_registry, facts(&remote_registry, "test/m"), 0)
.expect_err("remote refused under a local-only scope")
})
.await;
assert!(matches!(
refused,
InferenceError::SpendRefused { ref reason, ref message, .. }
if reason == REMOTE_IN_LOCAL_ONLY_SCOPE && message == REMOTE_IN_LOCAL_ONLY_SCOPE_MESSAGE
));
assert!(refused.raised_before_dispatch());
assert!(gate.admitted.lock().unwrap().is_empty());
let local = scope(unattended, async {
installed(&gate, true).admit(&local_registry, facts(&local_registry, "local/m"), 0)
})
.await;
assert!(local.is_ok(), "a local model runs unattended");
let ordinary = scope(SpendScope::new(SpendPrincipal::Car("heal".into())), async {
installed(&gate, true).admit(&remote_registry, facts(&remote_registry, "test/m"), 0)
})
.await;
assert!(ordinary.is_ok(), "only the local-only scope refuses");
}
#[tokio::test]
async fn a_local_only_gate_admits_a_local_dispatch() {
let registry = local_registry();
let local = facts(®istry, "local/m");
assert!(local.is_local);
assert!(local_only()
.admit(®istry, local.clone(), 0)
.unwrap()
.is_none());
let receipts = scope(
SpendScope::new(SpendPrincipal::Agent("bot".into())),
async {
let ticket = local_only()
.admit(®istry, local, 0)
.unwrap()
.expect("a ticket");
drop(ticket);
receipts()
},
)
.await;
assert_eq!(receipts.len(), 1);
assert_eq!(receipts[0].model_used, "local/m");
}
#[tokio::test]
async fn a_scoped_call_is_admitted_charged_and_receipted() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let installed = installed(&gate, true);
let scope_in = SpendScope::new(SpendPrincipal::Agent("bot".into()))
.with_keys(["label:orch/label-abc".to_string()]);
let receipts = scope(scope_in.clone(), async {
let mut ticket = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap()
.expect("a ticket");
let usage = TokenUsage {
prompt_tokens: 3,
completion_tokens: 4,
total_tokens: 7,
cache_read_input_tokens: 2,
..Default::default()
};
ticket.served("test/m", Some(&usage));
drop(ticket);
receipts()
})
.await;
let (scope_seen, facts_seen) = gate.admitted.lock().unwrap()[0].clone();
assert_eq!(scope_seen.principal, SpendPrincipal::Agent("bot".into()));
assert_eq!(
scope_seen.keys,
vec!["agent:bot".to_string(), "label:orch/label-abc".to_string()]
);
assert_eq!(facts_seen.estimate.micros, Some(30_000_000));
let (ticket, settled) = gate.settled.lock().unwrap()[0].clone();
assert_eq!(ticket.principal, "agent:bot");
assert_eq!(settled.input_tokens, 5, "the cached prompt counts as input");
assert_eq!(settled.output_tokens, 4);
assert_eq!(settled.cost.micros, Some(7_000_000));
assert_eq!(settled.cost.source, CostSource::ListPrice);
assert!(!settled.voided);
assert_eq!(receipts.len(), 1);
assert_eq!(receipts[0].call_id, "call-1");
assert_eq!(receipts[0].cost_usd, Some(7.0));
assert_eq!(receipts[0].cause, SpendCause::Direct);
}
#[tokio::test]
async fn a_dropped_ticket_settles_at_the_input_estimate() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let installed = installed(&gate, true);
scope(SpendScope::new(SpendPrincipal::Operator), async {
let ticket = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap();
drop(ticket);
})
.await;
let (admitted_scope, facts) = gate.admitted.lock().unwrap()[0].clone();
assert_eq!(admitted_scope.principal, SpendPrincipal::Operator);
assert_eq!(
facts.estimate.micros,
Some(30_000_000),
"admitted at the ceiling"
);
let (_, settled) = gate.settled.lock().unwrap()[0].clone();
assert_eq!(
settled.cost.micros,
Some(10_000_000),
"settled at the input alone"
);
assert_eq!(settled.input_tokens, 10);
assert_eq!(settled.output_tokens, 0);
}
#[tokio::test]
async fn a_refusal_is_charged_what_the_provider_reported() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let installed = installed(&gate, true);
scope(SpendScope::new(SpendPrincipal::Operator), async {
let mut ticket = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap()
.unwrap();
ticket.ended_with(&InferenceError::ContentRefused {
provider: "anthropic".into(),
kind: Some("refusal".into()),
code: None,
message: "declined".into(),
usage: Some(TokenUsage {
prompt_tokens: 12,
completion_tokens: 7,
total_tokens: 19,
..Default::default()
}),
});
drop(ticket);
})
.await;
let (_, settled) = gate.settled.lock().unwrap()[0].clone();
assert!(!settled.voided);
assert_eq!(settled.input_tokens, 12);
assert_eq!(settled.output_tokens, 7);
}
#[tokio::test]
async fn an_error_raised_before_dispatch_charges_nothing() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let installed = installed(&gate, true);
let receipts = scope(SpendScope::new(SpendPrincipal::Operator), async {
let mut ticket = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap()
.unwrap();
ticket.ended_with(&InferenceError::ProviderKeyMissing {
provider: "test".into(),
model: "test/m".into(),
env_vars: vec!["TEST_KEY".into()],
message: String::new(),
});
drop(ticket);
receipts()
})
.await;
let (_, settled) = gate.settled.lock().unwrap()[0].clone();
assert!(settled.voided);
assert_eq!(settled.cost.micros, None, "no cost to report");
assert_eq!(settled.cost.source, CostSource::Unknown);
assert!(receipts[0].voided);
assert_eq!(receipts[0].cost_usd, None);
}
#[tokio::test]
async fn a_fallback_attempt_names_its_cause_whatever_the_requests_cause() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let installed = installed(&gate, true);
scope(
SpendScope::new(SpendPrincipal::Operator).with_cause(SpendCause::Chat),
async {
let _first = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap();
let _second = installed
.admit(®istry, facts(®istry, "test/m"), 1)
.unwrap();
},
)
.await;
{
let admitted = gate.admitted.lock().unwrap();
assert_eq!(admitted[0].0.cause, SpendCause::Chat);
assert_eq!(admitted[1].0.cause, SpendCause::Fallback);
}
gate.admitted.lock().unwrap().clear();
scope(SpendScope::new(SpendPrincipal::Operator), async {
let _first = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap();
let _second = installed
.admit(®istry, facts(®istry, "test/m"), 1)
.unwrap();
})
.await;
let admitted = gate.admitted.lock().unwrap();
assert_eq!(admitted[0].0.cause, SpendCause::Direct);
assert_eq!(admitted[1].0.cause, SpendCause::Fallback);
}
#[tokio::test]
async fn spawn_scoped_carries_the_scope_and_a_plain_spawn_does_not() {
let carried = scope(SpendScope::new(SpendPrincipal::Car("test".into())), async {
let scoped = spawn_scoped(async { current().map(|s| s.principal.key()) })
.await
.unwrap();
let plain = tokio::spawn(async { current().map(|s| s.principal.key()) })
.await
.unwrap();
(scoped, plain)
})
.await;
assert_eq!(carried, (Some("car:test".to_string()), None));
}
#[tokio::test]
async fn decision_class_survives_nested_work_and_scoped_spawns() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let installed = installed(&gate, true);
scope(SpendScope::new(SpendPrincipal::Operator), async {
with_decision_class(Some("scheduling replies".into()), async {
spawn_scoped(async move {
scope(
SpendScope::new(SpendPrincipal::Car("helper".into())),
with_decision_class(None, async {
let ticket = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap();
drop(ticket);
}),
)
.await;
})
.await
.unwrap();
})
.await;
assert!(receipts().is_empty());
assert!(current().unwrap().decision_class.is_none());
})
.await;
assert_eq!(
gate.admitted.lock().unwrap()[0].0.decision_class.as_deref(),
Some("scheduling replies")
);
}
#[tokio::test]
async fn with_cause_rescopes_and_shares_receipts() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let installed = installed(&gate, true);
let outer = scope(SpendScope::new(SpendPrincipal::Operator), async {
with_cause(SpendCause::Summary, async {
let ticket = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap();
drop(ticket);
})
.await;
receipts()
})
.await;
assert_eq!(outer.len(), 1);
assert_eq!(outer[0].cause, SpendCause::Summary);
assert_eq!(
gate.admitted.lock().unwrap()[0].0.cause,
SpendCause::Summary
);
}
#[tokio::test]
async fn a_record_finds_its_own_calls_receipt_and_the_scope_is_bounded() {
let registry = registry_with("test/m", 1.0);
let gate = Arc::new(RecordingGate::default());
let installed = installed(&gate, true);
scope(SpendScope::new(SpendPrincipal::Operator), async {
let mut first = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap()
.unwrap();
first.set_trace("t-1");
let mut second = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap()
.unwrap();
second.set_trace("t-2");
let second_receipt = second.settle();
let first_receipt = first.settle();
assert_eq!(
receipt_for_trace("t-1").unwrap().call_id,
first_receipt.call_id
);
assert_eq!(
receipt_for_trace("t-2").unwrap().call_id,
second_receipt.call_id
);
assert!(receipt_for_trace("t-9").is_none());
for n in 0..RECEIPTS_KEPT + 10 {
let mut ticket = installed
.admit(®istry, facts(®istry, "test/m"), 0)
.unwrap()
.unwrap();
ticket.set_trace(&format!("t-bulk-{n}"));
drop(ticket);
}
assert_eq!(
receipts().len(),
RECEIPTS_KEPT,
"the scope keeps the newest"
);
assert!(
receipt_for_trace("t-1").is_none(),
"the oldest were dropped"
);
assert!(receipt_for_trace(&format!("t-bulk-{}", RECEIPTS_KEPT + 9)).is_some());
})
.await;
}
#[test]
fn principal_keys_round_trip_the_meter_spelling() {
assert_eq!(SpendPrincipal::Agent("a".into()).key(), "agent:a");
assert_eq!(SpendPrincipal::Worker("wk_1".into()).key(), "worker:wk_1");
assert_eq!(SpendPrincipal::Credential("c".into()).key(), "credential:c");
assert_eq!(SpendPrincipal::Operator.key(), "operator");
assert_eq!(SpendPrincipal::Car("selfheal".into()).key(), "car:selfheal");
assert_eq!(
SpendPrincipal::Declarative("d".into()).key(),
"declarative:d"
);
}
}