Skip to main content

lean_ctx/proxy/
usage_parity.rs

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