fix(cost): make dispatch billing evidence structurally absent until dispatch
Blocker group 2 (effective-route receipt provenance) and group 5
(unknown must survive aggregation), at the type level.
`TurnRoute` carried `billing_surface`, `endpoint_fingerprint`,
`billing_mode`, and `dispatched_at` as flat fields, which forced the
engine to fill them at `TurnStarted` — before anything had been
dispatched. The values were provisional but indistinguishable from real
ones, so every consumer downstream could read a metering surface and a
billing instant off a route that had never been sent. The candidate's
workaround was to emit `TurnStarted { route: None }`, which discarded
the installed-route receipt the reasoning-route-truth series
(039f8d81b..ed8bdf395) added for host authorization.
Both are now true. The four fields move into
`RouteBillingEnvelope`, held as `TurnRoute::billing: Option<_>`:
- `TurnStarted` carries the route (with its receipt) and `billing: None`.
A lifecycle start has no billing time to report, so it reports none.
- `RouteDispatched` carries the same route with `billing: Some(..)`
stamped at the wire boundary in `handle_deepseek_turn`.
- `TurnRoute::cost_envelope()` returns `Option<EffectiveRouteEnvelope>`.
There is no way to price an undispatched route, because there is no
envelope to price. Callers must decide what unknown means instead of
silently inheriting a zeroed default.
An injected model client gets no billing envelope at all. `deepseek_client`
is only an auxiliary route-shaping client there; it cannot attest the
endpoint that was actually billed, so the turn prices as unknown rather
than borrowing a surface from config. This is the rule that already
governs `TurnRoute::receipt` and `TurnComplete::base_url`.
Three fixtures were asserting against behavior that never held:
- Two used hand-written endpoint fingerprints
("served-endpoint-fingerprint", "test-anthropic-endpoint"). Receipt and
persistence boundaries fail closed on anything that is not 64 hex
digits, so both fields were being dropped — one assertion was failing,
the other was passing vacuously with the field absent on both sides.
Both now use the production hasher.
- `test_tokens_shows_usage_info` still expected "Approx session cost:".
That label is gone on purpose: the figure is a priced subtotal, and a
session with no priced turn now reports `unknown`. The test asserts the
new label and the unknown value.
This commit is contained in:
@@ -70,7 +70,11 @@ fn test_tokens_shows_usage_info() {
|
||||
assert!(msg.contains("Cache hit/miss:"));
|
||||
assert!(msg.contains("70 hit / 30 miss"));
|
||||
assert!(msg.contains("Cumulative tokens:"));
|
||||
assert!(msg.contains("Approx session cost:"));
|
||||
// Not "approx session cost": the figure is the priced subtotal, and a
|
||||
// session with no priced turn reports `unknown` rather than the raw
|
||||
// accumulator (#4318).
|
||||
assert!(msg.contains("Priced amount:"));
|
||||
assert!(msg.contains("Priced amount: unknown"), "{msg}");
|
||||
assert!(msg.contains("API messages:"));
|
||||
assert!(msg.contains("Chat messages:"));
|
||||
assert!(msg.contains("Model:"));
|
||||
|
||||
@@ -3707,21 +3707,37 @@ impl Engine {
|
||||
model: model.clone(),
|
||||
auto_model,
|
||||
receipt: route_receipt,
|
||||
billing_surface: crate::route_billing::billing_surface_for_dispatch(
|
||||
Some(&self.api_config),
|
||||
effective_provider,
|
||||
route_base_url,
|
||||
)
|
||||
.map(str::to_string),
|
||||
endpoint_fingerprint: route_base_url.and_then(crate::cost_status::endpoint_fingerprint),
|
||||
billing_mode: crate::route_billing::for_route(&self.api_config, effective_provider)
|
||||
.into(),
|
||||
// Provisional. Replaced with the true wire-boundary instant when
|
||||
// `handle_deepseek_turn` emits `Event::RouteDispatched`; billing
|
||||
// reads the dispatched envelope, never this one.
|
||||
dispatched_at: turn_started_at,
|
||||
// A start is not a dispatch. The billing envelope is attached
|
||||
// below, on the route held for the wire boundary only.
|
||||
billing: None,
|
||||
};
|
||||
turn.pending_route = Some(turn_route.clone());
|
||||
// An injected model client performs the I/O; `deepseek_client` is only
|
||||
// an auxiliary route-shaping client and cannot attest the endpoint that
|
||||
// was actually billed. Rather than fabricate a surface from config, the
|
||||
// route is dispatched with no billing envelope and prices as unknown —
|
||||
// the same rule that already governs `receipt` and `TurnComplete`'s
|
||||
// `base_url`.
|
||||
let dispatch_billing = (!self.model_client_injected).then(|| {
|
||||
crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: crate::route_billing::billing_surface_for_dispatch(
|
||||
Some(&self.api_config),
|
||||
effective_provider,
|
||||
route_base_url,
|
||||
)
|
||||
.map(str::to_string),
|
||||
endpoint_fingerprint: route_base_url
|
||||
.and_then(crate::cost_status::endpoint_fingerprint),
|
||||
billing_mode: crate::route_billing::for_route(&self.api_config, effective_provider)
|
||||
.into(),
|
||||
// Provisional. Replaced with the true wire-boundary instant
|
||||
// when `handle_deepseek_turn` emits `Event::RouteDispatched`.
|
||||
dispatched_at: turn_started_at,
|
||||
}
|
||||
});
|
||||
turn.pending_route = Some(TurnRoute {
|
||||
billing: dispatch_billing,
|
||||
..turn_route.clone()
|
||||
});
|
||||
|
||||
// Emit turn started event IMMEDIATELY so the UI knows the turn is
|
||||
// active. The snapshot below can take 30+ seconds on slow filesystems
|
||||
|
||||
@@ -307,8 +307,12 @@ async fn exact_turn_snapshot_restores_custom_endpoint_and_turn_receipt_after_bui
|
||||
lifecycle_stage, 0,
|
||||
"duplicate/reordered start: {diagnostics:?}"
|
||||
);
|
||||
// Lifecycle start still carries the installed-route receipt
|
||||
// hosts authorize follow-up work against, but it must carry no
|
||||
// billing envelope: nothing has been dispatched yet, and an
|
||||
// undispatched route has no metering surface or billing time.
|
||||
assert!(
|
||||
route.is_none(),
|
||||
route.as_ref().is_none_or(|route| route.billing.is_none()),
|
||||
"billing route must not be stamped at lifecycle start"
|
||||
);
|
||||
lifecycle_stage = 1;
|
||||
@@ -322,7 +326,10 @@ async fn exact_turn_snapshot_restores_custom_endpoint_and_turn_receipt_after_bui
|
||||
assert_eq!(route.provider_identity, "custom-a");
|
||||
assert_eq!(route.model, "local-model");
|
||||
assert_eq!(
|
||||
route.endpoint_fingerprint,
|
||||
route
|
||||
.billing
|
||||
.as_ref()
|
||||
.and_then(|billing| billing.endpoint_fingerprint.clone()),
|
||||
crate::cost_status::endpoint_fingerprint(&custom_base_url),
|
||||
"dispatch receipt borrowed the later ambient route"
|
||||
);
|
||||
@@ -675,7 +682,7 @@ async fn goal_continuation_preserves_goal_and_resolves_updated_authoritative_rou
|
||||
match event {
|
||||
Event::TurnStarted { route, .. } => {
|
||||
assert!(
|
||||
route.is_none(),
|
||||
route.as_ref().is_none_or(|route| route.billing.is_none()),
|
||||
"lifecycle start must not carry billing time"
|
||||
);
|
||||
lifecycle_starts += 1;
|
||||
@@ -692,7 +699,10 @@ async fn goal_continuation_preserves_goal_and_resolves_updated_authoritative_rou
|
||||
second_base_url.as_str()
|
||||
};
|
||||
assert_eq!(
|
||||
route.endpoint_fingerprint,
|
||||
route
|
||||
.billing
|
||||
.as_ref()
|
||||
.and_then(|billing| billing.endpoint_fingerprint.clone()),
|
||||
crate::cost_status::endpoint_fingerprint(expected_base_url),
|
||||
"goal continuation dispatch borrowed the wrong authoritative route"
|
||||
);
|
||||
|
||||
@@ -725,7 +725,9 @@ impl Engine {
|
||||
})
|
||||
.await;
|
||||
if let Some(mut route) = turn.pending_route.take() {
|
||||
route.dispatched_at = chrono::Utc::now();
|
||||
if let Some(billing) = route.billing.as_mut() {
|
||||
billing.dispatched_at = chrono::Utc::now();
|
||||
}
|
||||
let _ = self
|
||||
.tx_event
|
||||
.send(Event::RouteDispatched {
|
||||
|
||||
@@ -48,9 +48,22 @@ pub struct TurnRoute {
|
||||
/// `None` when no concrete client was installed (injected-client engines,
|
||||
/// or a client that failed to construct).
|
||||
pub receipt: Option<crate::route_receipt::TurnRouteReceipt>,
|
||||
/// Billing evidence captured with this exact effective route before the
|
||||
/// request starts; completion must never reconstruct it from mutable app
|
||||
/// config.
|
||||
/// Billing evidence for the request that was actually put on the wire.
|
||||
///
|
||||
/// `None` at `TurnStarted`: a lifecycle start is not a dispatch, and a
|
||||
/// route that has not been sent has no billing time, no metering surface,
|
||||
/// and no endpoint to attest. Populated exactly once, at the wire
|
||||
/// boundary, and delivered on `RouteDispatched`. Consumers that price a
|
||||
/// turn must treat `None` as *unknown*, never as a zero-cost turn.
|
||||
pub billing: Option<RouteBillingEnvelope>,
|
||||
}
|
||||
|
||||
/// Dispatch-time billing evidence. Separate from [`TurnRoute`] so the type
|
||||
/// system — not a convention — enforces that no caller can read a billing
|
||||
/// surface, endpoint fingerprint, or dispatch instant off a route that was
|
||||
/// only *planned*.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RouteBillingEnvelope {
|
||||
pub billing_surface: Option<String>,
|
||||
pub endpoint_fingerprint: Option<String>,
|
||||
pub billing_mode: crate::cost_status::RouteBillingMode,
|
||||
@@ -58,17 +71,21 @@ pub struct TurnRoute {
|
||||
}
|
||||
|
||||
impl TurnRoute {
|
||||
/// Priceable envelope for this route, or `None` when the route was never
|
||||
/// dispatched. Deliberately not a `Default`-filled envelope: an undispatched
|
||||
/// route has no cost, and "no cost" is not "zero cost".
|
||||
#[must_use]
|
||||
pub fn cost_envelope(&self) -> crate::cost_status::EffectiveRouteEnvelope {
|
||||
crate::cost_status::EffectiveRouteEnvelope {
|
||||
pub fn cost_envelope(&self) -> Option<crate::cost_status::EffectiveRouteEnvelope> {
|
||||
let billing = self.billing.as_ref()?;
|
||||
Some(crate::cost_status::EffectiveRouteEnvelope {
|
||||
provider: self.provider,
|
||||
provider_identity: self.provider_identity.clone(),
|
||||
model: self.model.clone(),
|
||||
billing_surface: self.billing_surface.clone(),
|
||||
endpoint_fingerprint: self.endpoint_fingerprint.clone(),
|
||||
billing_mode: self.billing_mode,
|
||||
dispatched_at: self.dispatched_at,
|
||||
}
|
||||
billing_surface: billing.billing_surface.clone(),
|
||||
endpoint_fingerprint: billing.endpoint_fingerprint.clone(),
|
||||
billing_mode: billing.billing_mode,
|
||||
dispatched_at: billing.dispatched_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5896,8 +5896,13 @@ impl RuntimeThreadManager {
|
||||
let _turn_mutation = self.store.turn_mutation.lock();
|
||||
let mut turn = self.store.load_turn(&turn_id)?;
|
||||
turn.started_at = Some(created_at);
|
||||
if let Some(route) = route {
|
||||
let route = route.cost_envelope();
|
||||
// A lifecycle start carries no billing envelope, so
|
||||
// there is nothing to persist yet. The dispatch event
|
||||
// below is the only writer of effective-route columns.
|
||||
if let Some(route) = route
|
||||
.as_ref()
|
||||
.and_then(crate::core::events::TurnRoute::cost_envelope)
|
||||
{
|
||||
turn.persist_effective_route(&route);
|
||||
}
|
||||
self.store.save_turn(&turn)?;
|
||||
@@ -5921,7 +5926,9 @@ impl RuntimeThreadManager {
|
||||
{
|
||||
let _turn_mutation = self.store.turn_mutation.lock();
|
||||
let mut turn = self.store.load_turn(&turn_id)?;
|
||||
turn.persist_effective_route(&route.cost_envelope());
|
||||
if let Some(envelope) = route.cost_envelope() {
|
||||
turn.persist_effective_route(&envelope);
|
||||
}
|
||||
self.store.save_turn(&turn)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3150,10 +3150,12 @@ async fn monitor_separates_lifecycle_start_from_billing_dispatch_and_child_usage
|
||||
model: "step-3.7-flash".to_string(),
|
||||
auto_model: false,
|
||||
receipt: None,
|
||||
billing_surface: Some(crate::pricing::STEPFUN_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some(endpoint_fingerprint.clone()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at,
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: Some(crate::pricing::STEPFUN_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some(endpoint_fingerprint.clone()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at,
|
||||
}),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -805,10 +805,12 @@ fn active_turn_zai_receipt_overrides_all_mutable_parallel_route_metadata() {
|
||||
crate::config::DEFAULT_ZAI_BASE_URL,
|
||||
"test-secret-never-persisted",
|
||||
)),
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}),
|
||||
auto_route_receipt: None,
|
||||
suggestion_authority: None,
|
||||
|
||||
@@ -1182,10 +1182,12 @@ mod tests {
|
||||
model,
|
||||
auto_model: false,
|
||||
receipt: Some(validated.client.turn_route_receipt("deepseek")),
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1526,10 +1528,12 @@ mod tests {
|
||||
DEEPSEEK_BASE,
|
||||
DEEPSEEK_KEY,
|
||||
)),
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
};
|
||||
assert!(
|
||||
capture_route_authority(&route).is_none(),
|
||||
|
||||
@@ -3524,7 +3524,8 @@ async fn run_event_loop(
|
||||
let cost_audit = completed_turn
|
||||
.as_ref()
|
||||
.and_then(|turn| turn.route.as_ref())
|
||||
.map(|route| route.cost_envelope().audit(&usage));
|
||||
.and_then(crate::core::events::TurnRoute::cost_envelope)
|
||||
.map(|route| route.audit(&usage));
|
||||
app.push_turn_cache_record(crate::tui::app::TurnCacheRecord {
|
||||
provider,
|
||||
provider_identity,
|
||||
@@ -3796,7 +3797,8 @@ async fn run_event_loop(
|
||||
completed_turn
|
||||
.as_ref()
|
||||
.and_then(|turn| turn.route.as_ref())
|
||||
.and_then(|route| route.billing_surface.as_deref()),
|
||||
.and_then(|route| route.billing.as_ref())
|
||||
.and_then(|billing| billing.billing_surface.as_deref()),
|
||||
turn_elapsed,
|
||||
error.as_deref(),
|
||||
) {
|
||||
@@ -17984,7 +17986,7 @@ fn completed_turn_cost_route_receipt(
|
||||
audit: &crate::pricing::TurnCostAudit,
|
||||
) -> Option<String> {
|
||||
let route = completed_turn?.route.as_ref()?;
|
||||
Some(route.cost_envelope().receipt(audit))
|
||||
Some(route.cost_envelope()?.receipt(audit))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -51,6 +51,10 @@ fn test_mailbox_route(
|
||||
)
|
||||
}
|
||||
|
||||
fn served_endpoint_fingerprint() -> Option<String> {
|
||||
crate::cost_status::endpoint_fingerprint("https://api.deepseek.com/v1")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_turn_cost_receipt_uses_the_captured_effective_route() {
|
||||
let usage = crate::models::Usage {
|
||||
@@ -74,10 +78,15 @@ fn completed_turn_cost_receipt_uses_the_captured_effective_route() {
|
||||
model: "deepseek-v4-flash".to_string(),
|
||||
auto_model: false,
|
||||
receipt: None,
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("served-endpoint-fingerprint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
// A real fingerprint, produced by the production hasher. A
|
||||
// receipt fails closed on anything that is not one, so a
|
||||
// hand-written placeholder here would have tested nothing.
|
||||
endpoint_fingerprint: served_endpoint_fingerprint(),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}),
|
||||
auto_route_receipt: None,
|
||||
suggestion_authority: None,
|
||||
@@ -90,7 +99,10 @@ fn completed_turn_cost_receipt_uses_the_captured_effective_route() {
|
||||
assert!(receipt.contains("identity=deepseek-cn"), "{receipt}");
|
||||
assert!(receipt.contains("model=deepseek-v4-flash"), "{receipt}");
|
||||
assert!(
|
||||
receipt.contains("endpoint_fp=served-endpoint-fingerprint"),
|
||||
receipt.contains(&format!(
|
||||
"endpoint_fp={}",
|
||||
served_endpoint_fingerprint().expect("fingerprintable endpoint")
|
||||
)),
|
||||
"{receipt}"
|
||||
);
|
||||
assert!(receipt.contains("billing_mode=metered"), "{receipt}");
|
||||
@@ -7530,10 +7542,12 @@ fn turn_liveness_recovers_stalled_in_progress_turn() {
|
||||
model: "gpt-5.5".to_string(),
|
||||
auto_model: false,
|
||||
receipt: None,
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}),
|
||||
auto_route_receipt: None,
|
||||
suggestion_authority: None,
|
||||
@@ -7577,10 +7591,12 @@ fn engine_event_disconnect_recovers_live_turn_immediately() {
|
||||
model: "gpt-5.5".to_string(),
|
||||
auto_model: false,
|
||||
receipt: None,
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}),
|
||||
auto_route_receipt: None,
|
||||
suggestion_authority: None,
|
||||
@@ -7648,10 +7664,12 @@ fn engine_event_disconnect_cleans_cancelled_turn_metadata() {
|
||||
model: "gpt-5.5".to_string(),
|
||||
auto_model: false,
|
||||
receipt: None,
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}),
|
||||
auto_route_receipt: None,
|
||||
suggestion_authority: None,
|
||||
@@ -10104,10 +10122,12 @@ fn turn_started_route_is_captured_before_cancel_suppression() {
|
||||
model: "gpt-5.5".to_string(),
|
||||
auto_model: true,
|
||||
receipt: None,
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: created_at.clone(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: created_at.clone(),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -10164,10 +10184,12 @@ fn turn_started_suggestion_authority_comes_from_the_route_receipt_not_config() {
|
||||
model: "deepseek-chat".to_string(),
|
||||
auto_model: false,
|
||||
receipt: Some(receipt),
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -10202,10 +10224,12 @@ fn turn_started_without_a_route_receipt_captures_no_suggestion_authority() {
|
||||
model: "deepseek-chat".to_string(),
|
||||
auto_model: false,
|
||||
receipt: None,
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: None,
|
||||
endpoint_fingerprint: None,
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Unknown,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -10234,10 +10258,12 @@ fn engine_error_health_accounting_uses_active_turn_route() {
|
||||
model: "gpt-5.5".to_string(),
|
||||
auto_model: true,
|
||||
receipt: None,
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
billing: Some(crate::core::events::RouteBillingEnvelope {
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("openai-endpoint".to_string()),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::Utc::now(),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
capture_turn_started_metadata(&mut app, &event);
|
||||
@@ -13230,7 +13256,12 @@ fn child_usage_metadata_carries_cache_write_and_reasoning_end_to_end() {
|
||||
provider_identity: "anthropic-api".to_string(),
|
||||
model: "claude-haiku-4-5".to_string(),
|
||||
billing_surface: Some(crate::pricing::FIRST_PARTY_PAYG_BILLING_SURFACE.to_string()),
|
||||
endpoint_fingerprint: Some("test-anthropic-endpoint".to_string()),
|
||||
// Must be a real fingerprint: persistence and receipt boundaries drop
|
||||
// anything that is not one, and a dropped field would have made the
|
||||
// round-trip assertion below vacuously pass on both sides.
|
||||
endpoint_fingerprint: crate::cost_status::endpoint_fingerprint(
|
||||
"https://api.anthropic.com/v1",
|
||||
),
|
||||
billing_mode: crate::cost_status::RouteBillingMode::Metered,
|
||||
dispatched_at: chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0).expect("epoch"),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user