1use crate::LlmModel;
2use crate::Result as LlmResult;
3use std::pin::Pin;
4use tokio_stream::Stream;
5
6use super::{Context, LlmResponse};
7
8pub type LlmResponseStream = Pin<Box<dyn Stream<Item = LlmResult<LlmResponse>> + Send>>;
11
12pub trait ProviderFactory: Sized {
17 fn from_env() -> super::Result<Self>;
19
20 fn with_model(self, model: &str) -> Self;
22}
23
24pub trait StreamingModelProvider: Send + Sync {
25 fn stream_response(&self, context: &Context) -> LlmResponseStream;
26 fn display_name(&self) -> String;
27
28 fn context_window(&self) -> Option<u32>;
31
32 fn model(&self) -> Option<LlmModel> {
36 None
37 }
38}
39
40pub fn get_context_window(provider: &str, model_id: &str) -> Option<u32> {
44 let key = format!("{provider}:{model_id}");
45 key.parse::<LlmModel>()
46 .ok()
47 .and_then(|m| m.context_window())
48}
49
50impl StreamingModelProvider for Box<dyn StreamingModelProvider> {
51 fn stream_response(&self, context: &Context) -> LlmResponseStream {
52 (**self).stream_response(context)
53 }
54
55 fn display_name(&self) -> String {
56 (**self).display_name()
57 }
58
59 fn context_window(&self) -> Option<u32> {
60 (**self).context_window()
61 }
62
63 fn model(&self) -> Option<LlmModel> {
64 (**self).model()
65 }
66}
67
68impl<T: StreamingModelProvider> StreamingModelProvider for std::sync::Arc<T> {
69 fn stream_response(&self, context: &Context) -> LlmResponseStream {
70 (**self).stream_response(context)
71 }
72
73 fn display_name(&self) -> String {
74 (**self).display_name()
75 }
76
77 fn context_window(&self) -> Option<u32> {
78 (**self).context_window()
79 }
80
81 fn model(&self) -> Option<LlmModel> {
82 (**self).model()
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn lookup_context_window_known_model() {
92 assert_eq!(
93 get_context_window("anthropic", "claude-opus-4-6"),
94 Some(1_000_000)
95 );
96 }
97
98 #[test]
99 fn lookup_context_window_openrouter_model() {
100 let result = get_context_window("openrouter", "anthropic/claude-opus-4");
102 assert_eq!(result, Some(200_000));
103 }
104
105 #[test]
106 fn lookup_context_window_unknown_model() {
107 assert_eq!(get_context_window("anthropic", "unknown-model-xyz"), None);
108 }
109
110 #[test]
111 fn lookup_context_window_unknown_provider() {
112 assert_eq!(get_context_window("unknown-provider", "some-model"), None);
113 }
114}