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}