Skip to main content

bitrouter_core/
observe.rs

1//! Handler-level observation callback for request lifecycle events.
2//!
3//! [`ObserveCallback`] fires from the API handler layer with full request
4//! context (route, provider, model, account, latency, usage/error).
5//! This is complementary to [`GenerationHook`](crate::hooks::GenerationHook)
6//! which fires at the model layer with no request context.
7
8use std::future::Future;
9use std::pin::Pin;
10
11use crate::auth::claims::{BudgetRange, BudgetScope};
12use crate::errors::BitrouterError;
13use crate::models::language::usage::LanguageModelUsage;
14
15/// Authenticated caller context extracted from JWT claims.
16///
17/// Carries the account identifier and any claim-based permissions (budget,
18/// model allowlist) through the API handler layer. Constructed in the auth
19/// filter and consumed by observers and (in the future) enforcement middleware.
20#[derive(Debug, Clone, Default)]
21pub struct CallerContext {
22    /// The account that made the request, if authentication is enabled.
23    pub account_id: Option<String>,
24    /// Optional model-name patterns this caller may access.
25    pub models: Option<Vec<String>>,
26    /// Budget limit in micro USD.
27    pub budget: Option<u64>,
28    /// Whether the budget applies per-session or per-account.
29    pub budget_scope: Option<BudgetScope>,
30    /// The range over which the budget is measured.
31    pub budget_range: Option<BudgetRange>,
32    /// CAIP-2 chain identifier from JWT claims (e.g. `"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"`).
33    pub chain: Option<String>,
34}
35
36/// Context about the request available to observation callbacks.
37#[derive(Debug, Clone)]
38pub struct RequestContext {
39    /// The incoming model name (route key).
40    pub route: String,
41    /// The resolved provider name.
42    pub provider: String,
43    /// The resolved model ID sent to the provider.
44    pub model: String,
45    /// Authenticated caller context (account ID, budget claims).
46    pub caller: CallerContext,
47    /// End-to-end request latency in milliseconds.
48    pub latency_ms: u64,
49}
50
51/// Event emitted when a request completes successfully.
52#[derive(Debug, Clone)]
53pub struct RequestSuccessEvent {
54    /// Request context.
55    pub ctx: RequestContext,
56    /// Token usage reported by the model.
57    pub usage: LanguageModelUsage,
58}
59
60/// Event emitted when a request fails.
61#[derive(Debug, Clone)]
62pub struct RequestFailureEvent {
63    /// Request context.
64    pub ctx: RequestContext,
65    /// The error that caused the failure.
66    pub error: BitrouterError,
67}
68
69/// Callback trait for observing completed API requests.
70///
71/// Implementations receive rich, typed events with full request context
72/// and can perform side effects such as spend logging, metrics aggregation,
73/// or alerting. Errors in callbacks are expected to be handled internally
74/// (e.g. logged and swallowed) — they must never break request serving.
75///
76/// Methods return boxed futures for dyn-compatibility, allowing multiple
77/// observers to be composed via [`CompositeObserver`](see bitrouter-observe).
78pub trait ObserveCallback: Send + Sync {
79    /// Called after a request completes successfully.
80    fn on_request_success(
81        &self,
82        event: RequestSuccessEvent,
83    ) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
84
85    /// Called after a request fails.
86    fn on_request_failure(
87        &self,
88        event: RequestFailureEvent,
89    ) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
90}