lean_ctx/core/context_kernel/
client_wiring.rs1use super::client_profile::{self, ClientEfficiencyProfile};
4use super::context_broker::{BrokerBudget, ContextBroker};
5use super::coverage_class::{self, CoverageClass};
6use super::etpao_live::{EtpaoLive, OutcomeMetrics, RequestMetrics};
7use super::identity::CallerIdentity;
8use super::identity_resolver;
9
10#[derive(Debug, Clone)]
12pub struct RequestContext {
13 pub identity: CallerIdentity,
15 pub profile: ClientEfficiencyProfile,
17 pub coverage: CoverageClass,
19 pub broker_budget: BrokerBudget,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum OptimizationLevel {
26 Full,
28 #[default]
30 Partial,
31 ObserveOnly,
33 None,
35}
36
37#[must_use]
39pub fn build_request_context(
40 headers: &[(String, String)],
41 has_proxy: bool,
42 has_mcp: bool,
43 has_hooks: bool,
44) -> RequestContext {
45 let identity = identity_resolver::resolve_with_defaults(headers);
46 let coverage = coverage_class::detect_coverage(has_proxy, has_mcp, has_hooks);
47 let mut profile = client_profile::detect_from_headers(headers);
48 profile.coverage = coverage;
49 let broker_budget = ContextBroker::new(profile.clone()).compute_budget();
50
51 RequestContext {
52 identity,
53 profile,
54 coverage,
55 broker_budget,
56 }
57}
58
59#[must_use]
61pub fn should_optimize(ctx: &RequestContext) -> bool {
62 coverage_class::is_addressable(ctx.coverage)
63}
64
65#[must_use]
67pub fn optimization_level(ctx: &RequestContext) -> OptimizationLevel {
68 match ctx.coverage {
69 CoverageClass::FullInline => OptimizationLevel::Full,
70 CoverageClass::ContextControlled => OptimizationLevel::Partial,
71 CoverageClass::ObserveOnly => OptimizationLevel::ObserveOnly,
72 CoverageClass::Unmanaged => OptimizationLevel::None,
73 }
74}
75
76pub fn record_request_etpao(
78 etpao: &mut EtpaoLive,
79 ctx: &RequestContext,
80 input_tokens: usize,
81 output_tokens: usize,
82) {
83 etpao.record_request(RequestMetrics {
84 input_tokens,
85 output_tokens,
86 reasoning_tokens: 0,
87 schema_tokens: 0,
88 cache_write_tokens: 0,
89 retry_count: 0,
90 client_id: ctx.profile.client_id.clone(),
91 coverage_class: ctx.coverage,
92 });
93}
94
95pub fn record_outcome_etpao(
97 etpao: &mut EtpaoLive,
98 ctx: &RequestContext,
99 accepted: bool,
100 quality: f64,
101) {
102 etpao.record_outcome(OutcomeMetrics {
103 accepted,
104 quality_score: quality,
105 first_pass: accepted,
106 client_id: ctx.profile.client_id.clone(),
107 });
108}
109
110#[must_use]
112pub fn format_request_summary(ctx: &RequestContext) -> String {
113 format!(
114 "[{}] user={} team={} budget={}",
115 coverage_class::coverage_label(ctx.coverage),
116 ctx.identity.user_id.as_deref().unwrap_or("-"),
117 ctx.identity.team_id.as_deref().unwrap_or("-"),
118 ctx.broker_budget.context_tokens
119 )
120}
121
122#[cfg(test)]
123mod tests {
124 use super::{
125 OptimizationLevel, build_request_context, format_request_summary, optimization_level,
126 record_outcome_etpao, record_request_etpao, should_optimize,
127 };
128 use crate::core::context_kernel::coverage_class::CoverageClass;
129 use crate::core::context_kernel::etpao_live::EtpaoLive;
130
131 fn context(coverage: CoverageClass) -> super::RequestContext {
132 let (has_proxy, has_mcp, has_hooks) = match coverage {
133 CoverageClass::FullInline => (true, false, false),
134 CoverageClass::ContextControlled => (false, true, false),
135 CoverageClass::ObserveOnly => (false, false, true),
136 CoverageClass::Unmanaged => (false, false, false),
137 };
138 build_request_context(&[], has_proxy, has_mcp, has_hooks)
139 }
140
141 #[test]
142 fn build_context_from_empty_headers() {
143 let ctx = build_request_context(&[], false, false, false);
144 assert_eq!(ctx.profile.client_id, "unknown");
145 assert_eq!(ctx.profile.coverage, CoverageClass::Unmanaged);
146 assert_eq!(ctx.coverage, CoverageClass::Unmanaged);
147 assert!(ctx.broker_budget.context_tokens > 0);
148 }
149
150 #[test]
151 fn build_context_with_proxy() {
152 let ctx = build_request_context(&[], true, false, false);
153 assert_eq!(ctx.coverage, CoverageClass::FullInline);
154 assert_eq!(ctx.profile.coverage, CoverageClass::FullInline);
155 }
156
157 #[test]
158 fn should_optimize_full_inline() {
159 assert!(should_optimize(&context(CoverageClass::FullInline)));
160 }
161
162 #[test]
163 fn should_optimize_unmanaged() {
164 assert!(!should_optimize(&context(CoverageClass::Unmanaged)));
165 }
166
167 #[test]
168 fn optimization_level_mapping() {
169 for (coverage, expected) in [
170 (CoverageClass::FullInline, OptimizationLevel::Full),
171 (CoverageClass::ContextControlled, OptimizationLevel::Partial),
172 (CoverageClass::ObserveOnly, OptimizationLevel::ObserveOnly),
173 (CoverageClass::Unmanaged, OptimizationLevel::None),
174 ] {
175 assert_eq!(optimization_level(&context(coverage)), expected);
176 }
177 }
178
179 #[test]
180 fn format_summary_includes_identity() {
181 let ctx = build_request_context(&[], false, true, false);
182 let expected_user = format!("user={}", ctx.identity.user_id.as_deref().unwrap_or("-"));
183 let expected_team = format!("team={}", ctx.identity.team_id.as_deref().unwrap_or("-"));
184 let summary = format_request_summary(&ctx);
185 assert!(summary.contains(&expected_user));
186 assert!(summary.contains(&expected_team));
187 assert!(summary.contains("budget="));
188 }
189
190 #[test]
191 fn etpao_recording_works() {
192 let ctx = context(CoverageClass::FullInline);
193 let mut etpao = EtpaoLive::new();
194 record_request_etpao(&mut etpao, &ctx, 100, 25);
195 record_outcome_etpao(&mut etpao, &ctx, true, 0.9);
196
197 let summary = etpao.summary();
198 assert_eq!(etpao.request_count(), 1);
199 assert_eq!(etpao.outcome_count(), 1);
200 assert_eq!(summary.total_tokens, 125);
201 assert_eq!(summary.accepted_outcomes, 1);
202 }
203}