1use std::env;
39use std::marker::PhantomData;
40
41use chat_completions::CompletionsBuilder;
42pub use chat_completions::{CompletionsClient, ReqwestTransport};
43use chat_core::transport::Transport;
44use chat_responses::ResponsesBuilder;
45pub use chat_responses::ResponsesClient;
46
47pub const DEFAULT_OPENROUTER_BASE_URL: &str = "https://openrouter.ai/api/v1";
50
51const OPENROUTER_API_KEY_ENV: &str = "OPENROUTER_API_KEY";
52
53pub struct WithoutModel;
54pub struct WithModel;
55
56pub struct Responses;
58pub struct Completions;
60
61pub struct OpenRouterBuilder<M = WithoutModel, W = Responses, T: Transport = ReqwestTransport> {
62 model: Option<String>,
63 api_key: Option<String>,
64 base_url: String,
65 reasoning_effort: Option<String>,
66 description: Option<String>,
67 transport: Option<T>,
68 _m: PhantomData<M>,
69 _w: PhantomData<W>,
70}
71
72impl Default for OpenRouterBuilder<WithoutModel, Responses, ReqwestTransport> {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78impl OpenRouterBuilder<WithoutModel, Responses, ReqwestTransport> {
79 pub fn new() -> Self {
80 Self {
81 model: None,
82 api_key: None,
83 base_url: DEFAULT_OPENROUTER_BASE_URL.to_string(),
84 reasoning_effort: None,
85 description: None,
86 transport: Some(ReqwestTransport::default()),
87 _m: PhantomData,
88 _w: PhantomData,
89 }
90 }
91}
92
93impl<M, W, T: Transport> OpenRouterBuilder<M, W, T> {
94 pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
96 self.api_key = Some(api_key.into());
97 self
98 }
99
100 pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
102 self.base_url = url.into();
103 self
104 }
105
106 pub fn with_description(mut self, description: impl Into<String>) -> Self {
107 self.description = Some(description.into());
108 self
109 }
110
111 pub fn with_transport<T2: Transport>(self, transport: T2) -> OpenRouterBuilder<M, W, T2> {
113 OpenRouterBuilder {
114 model: self.model,
115 api_key: self.api_key,
116 base_url: self.base_url,
117 reasoning_effort: self.reasoning_effort,
118 description: self.description,
119 transport: Some(transport),
120 _m: PhantomData,
121 _w: PhantomData,
122 }
123 }
124}
125
126impl<W, T: Transport> OpenRouterBuilder<WithoutModel, W, T> {
127 pub fn with_model(self, model: impl Into<String>) -> OpenRouterBuilder<WithModel, W, T> {
130 OpenRouterBuilder {
131 model: Some(model.into()),
132 api_key: self.api_key,
133 base_url: self.base_url,
134 reasoning_effort: self.reasoning_effort,
135 description: self.description,
136 transport: self.transport,
137 _m: PhantomData,
138 _w: PhantomData,
139 }
140 }
141}
142
143impl<M, T: Transport> OpenRouterBuilder<M, Responses, T> {
144 pub fn with_completions(self) -> OpenRouterBuilder<M, Completions, T> {
146 OpenRouterBuilder {
147 model: self.model,
148 api_key: self.api_key,
149 base_url: self.base_url,
150 reasoning_effort: self.reasoning_effort,
151 description: self.description,
152 transport: self.transport,
153 _m: PhantomData,
154 _w: PhantomData,
155 }
156 }
157
158 pub fn with_reasoning_effort(mut self, effort: impl Into<String>) -> Self {
162 self.reasoning_effort = Some(effort.into());
163 self
164 }
165}
166
167impl<M, W, T: Transport> OpenRouterBuilder<M, W, T> {
168 fn resolve_api_key(&mut self) -> String {
169 self.api_key
170 .take()
171 .or_else(|| env::var(OPENROUTER_API_KEY_ENV).ok())
172 .expect("No OpenRouter API key. Set OPENROUTER_API_KEY or call .with_api_key().")
173 }
174}
175
176impl<T: Transport> OpenRouterBuilder<WithModel, Responses, T> {
177 pub fn build(mut self) -> ResponsesClient<T> {
179 let api_key = self.resolve_api_key();
180 let mut rb = ResponsesBuilder::new()
181 .with_base_url(self.base_url)
182 .with_model(self.model.expect("model set"))
183 .with_api_key(api_key)
184 .with_transport(self.transport.expect("transport set"))
185 .without_previous_response_id();
186
187 if let Some(eff) = self.reasoning_effort {
188 rb = rb.with_reasoning_effort(eff);
189 }
190 if let Some(desc) = self.description {
191 rb = rb.with_description(desc);
192 }
193 rb.build()
194 }
195}
196
197impl<T: Transport> OpenRouterBuilder<WithModel, Completions, T> {
198 pub fn build(mut self) -> CompletionsClient<T> {
200 let api_key = self.resolve_api_key();
201 let mut cb = CompletionsBuilder::new()
202 .with_base_url(self.base_url)
203 .with_model(self.model.expect("model set"))
204 .with_api_key(api_key)
205 .with_transport(self.transport.expect("transport set"));
206
207 if let Some(desc) = self.description {
208 cb = cb.with_description(desc);
209 }
210 cb.build()
211 }
212}