Skip to main content

lean_ctx_proxy/
usage_parity.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Proxy-to-kernel provider and usage conversion.
3
4use lean_ctx::core::context_kernel::provider_parity;
5use lean_ctx::core::context_kernel::token_envelope::{ProviderKind, TokenEnvelope};
6
7/// Maps a proxy provider label to its kernel provider kind.
8pub fn provider_kind_from_label(label: &str) -> ProviderKind {
9    match label {
10        "Anthropic" => ProviderKind::Anthropic,
11        "OpenAI" | "ChatGPT" => ProviderKind::OpenAi,
12        "Gemini" => ProviderKind::Gemini,
13        "Bedrock" => ProviderKind::Bedrock,
14        "Azure" => ProviderKind::Azure,
15        _ if label.to_ascii_lowercase().contains("openrouter") => ProviderKind::OpenRouter,
16        _ => ProviderKind::Unknown,
17    }
18}
19
20/// Converts proxy-reported usage into a provider-neutral kernel envelope.
21pub fn real_usage_to_envelope(usage: &super::usage::RealUsage, label: &str) -> TokenEnvelope {
22    TokenEnvelope {
23        model: usage.model.clone(),
24        provider: provider_kind_from_label(label),
25        input_tokens: usage.input_tokens as usize,
26        output_tokens: usage.output_tokens as usize,
27        cache_read_tokens: usage.cache_read_tokens as usize,
28        cache_write_tokens: usage.cache_write_tokens as usize,
29        reasoning_tokens: usage.reasoning_tokens as usize,
30        cost_usd: usage.provider_cost_usd,
31        tokens_saved: 0,
32        is_retry: false,
33    }
34}
35
36/// Detects a provider from a base URL and returns its canonical display label.
37pub fn label_from_base_url(base_url: &str) -> &'static str {
38    provider_parity::provider_display_name(provider_parity::detect_provider(base_url))
39}
40
41#[cfg(test)]
42mod tests {
43    use super::{label_from_base_url, provider_kind_from_label, real_usage_to_envelope};
44    use crate::usage::RealUsage;
45    use lean_ctx::core::context_kernel::token_envelope::ProviderKind;
46
47    #[test]
48    fn kind_from_anthropic_label() {
49        assert_eq!(
50            provider_kind_from_label("Anthropic"),
51            ProviderKind::Anthropic
52        );
53    }
54
55    #[test]
56    fn kind_from_openai_label() {
57        assert_eq!(provider_kind_from_label("OpenAI"), ProviderKind::OpenAi);
58    }
59
60    #[test]
61    fn kind_from_chatgpt_label() {
62        assert_eq!(provider_kind_from_label("ChatGPT"), ProviderKind::OpenAi);
63    }
64
65    #[test]
66    fn kind_from_bedrock_label() {
67        assert_eq!(provider_kind_from_label("Bedrock"), ProviderKind::Bedrock);
68    }
69
70    #[test]
71    fn kind_from_azure_label() {
72        assert_eq!(provider_kind_from_label("Azure"), ProviderKind::Azure);
73    }
74
75    #[test]
76    fn kind_from_gemini_label() {
77        assert_eq!(provider_kind_from_label("Gemini"), ProviderKind::Gemini);
78    }
79
80    #[test]
81    fn kind_from_unknown() {
82        assert_eq!(provider_kind_from_label("Other"), ProviderKind::Unknown);
83    }
84
85    #[test]
86    fn real_usage_full_convert() {
87        let usage = RealUsage {
88            model: "model-1".to_owned(),
89            input_tokens: 11,
90            output_tokens: 12,
91            cache_read_tokens: 13,
92            cache_write_tokens: 14,
93            reasoning_tokens: 15,
94            provider_cost_usd: Some(0.25),
95            ..RealUsage::default()
96        };
97
98        let envelope = real_usage_to_envelope(&usage, "Anthropic");
99
100        assert_eq!(envelope.model, "model-1");
101        assert_eq!(envelope.provider, ProviderKind::Anthropic);
102        assert_eq!(envelope.input_tokens, 11);
103        assert_eq!(envelope.output_tokens, 12);
104        assert_eq!(envelope.cache_read_tokens, 13);
105        assert_eq!(envelope.cache_write_tokens, 14);
106        assert_eq!(envelope.reasoning_tokens, 15);
107        assert_eq!(envelope.cost_usd, Some(0.25));
108    }
109
110    #[test]
111    fn label_from_url_openai() {
112        assert_eq!(label_from_base_url("https://api.openai.com/v1"), "OpenAI");
113    }
114}