1pub mod anthropic;
4pub mod circuit;
5
6const ERROR_BODY_MAX_BYTES: usize = 8 << 10; pub(crate) const STREAM_MAX_TEXT_BYTES: usize = 16 << 20; pub(crate) const STREAM_MAX_TOOL_ARGS_BYTES: usize = 1 << 20; pub(crate) const STREAM_MAX_TOOL_CALLS: usize = 256;
27
28pub(crate) const SUCCESS_BODY_MAX_BYTES: usize = 16 << 20; pub(crate) async fn read_json_capped<T: serde::de::DeserializeOwned>(
39 response: reqwest::Response,
40) -> Result<T, Error> {
41 let (bytes, truncated) =
42 crate::http::read_body_capped(response, SUCCESS_BODY_MAX_BYTES).await?;
43 if truncated {
44 return Err(Error::Api {
45 status: 200,
46 message: format!("response body exceeded {SUCCESS_BODY_MAX_BYTES}-byte cap"),
47 });
48 }
49 serde_json::from_slice(&bytes).map_err(|e| Error::Api {
50 status: 200,
51 message: format!("failed to parse response JSON: {e}"),
52 })
53}
54
55pub(crate) async fn api_error_from_response(response: reqwest::Response) -> Error {
63 use futures::TryStreamExt;
64 let status = response.status().as_u16();
65 let message = if status == 401 || status == 403 {
66 format!("authentication failed (HTTP {status})")
67 } else {
68 let mut buf: Vec<u8> = Vec::with_capacity(2048);
69 let mut stream = response.bytes_stream();
70 let mut overflowed = false;
71 loop {
72 match stream.try_next().await {
73 Ok(Some(chunk)) => {
74 let remaining = ERROR_BODY_MAX_BYTES.saturating_sub(buf.len());
75 if remaining == 0 {
76 overflowed = true;
77 break;
78 }
79 let take = chunk.len().min(remaining);
80 buf.extend_from_slice(&chunk[..take]);
81 if take < chunk.len() {
82 overflowed = true;
83 break;
84 }
85 }
86 Ok(None) => break,
87 Err(e) => {
88 return Error::Api {
89 status,
90 message: format!("<body read error: {e}>"),
91 };
92 }
93 }
94 }
95 let mut text = String::from_utf8_lossy(&buf).to_string();
96 text.retain(|c| c == '\t' || (!c.is_control() && c != '\u{1b}'));
98 if overflowed {
99 text.push_str("…[truncated]");
100 }
101 text
102 };
103 Error::Api { status, message }
104}
105pub mod cascade;
106pub mod error_class;
107pub mod gemini;
108pub mod openai_compat;
109pub mod openrouter;
110pub mod pricing;
111pub mod registry;
112pub mod retry;
113pub mod types;
114
115use std::future::Future;
116use std::pin::Pin;
117use std::sync::Arc;
118
119use crate::error::Error;
120use crate::llm::types::{CompletionRequest, CompletionResponse};
121
122pub type OnText = dyn Fn(&str) + Send + Sync;
124
125pub type OnReasoning = dyn Fn(&str) + Send + Sync;
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum ApprovalDecision {
137 Allow,
139 Deny,
141 AlwaysAllow,
143 AlwaysDeny,
145}
146
147impl ApprovalDecision {
148 pub fn is_allowed(self) -> bool {
150 matches!(self, Self::Allow | Self::AlwaysAllow)
151 }
152
153 pub fn is_persistent(self) -> bool {
155 matches!(self, Self::AlwaysAllow | Self::AlwaysDeny)
156 }
157}
158
159impl From<bool> for ApprovalDecision {
160 fn from(allowed: bool) -> Self {
161 if allowed { Self::Allow } else { Self::Deny }
162 }
163}
164
165pub type OnApproval = dyn Fn(&[crate::llm::types::ToolCall]) -> ApprovalDecision + Send + Sync;
172
173pub trait LlmProvider: Send + Sync {
182 fn complete(
184 &self,
185 request: CompletionRequest,
186 ) -> impl Future<Output = Result<CompletionResponse, Error>> + Send;
187
188 fn stream_complete(
195 &self,
196 request: CompletionRequest,
197 on_text: &OnText,
198 ) -> impl Future<Output = Result<CompletionResponse, Error>> + Send {
199 let _ = on_text;
200 self.complete(request)
201 }
202
203 fn stream_complete_with_reasoning(
212 &self,
213 request: CompletionRequest,
214 on_text: &OnText,
215 on_reasoning: &OnReasoning,
216 ) -> impl Future<Output = Result<CompletionResponse, Error>> + Send {
217 let _ = on_reasoning;
218 self.stream_complete(request, on_text)
219 }
220
221 fn model_name(&self) -> Option<&str> {
225 None
226 }
227}
228
229pub trait DynLlmProvider: Send + Sync {
243 fn complete<'a>(
245 &'a self,
246 request: CompletionRequest,
247 ) -> Pin<Box<dyn Future<Output = Result<CompletionResponse, Error>> + Send + 'a>>;
248
249 fn stream_complete<'a>(
251 &'a self,
252 request: CompletionRequest,
253 on_text: &'a OnText,
254 ) -> Pin<Box<dyn Future<Output = Result<CompletionResponse, Error>> + Send + 'a>>;
255
256 fn stream_complete_with_reasoning<'a>(
262 &'a self,
263 request: CompletionRequest,
264 on_text: &'a OnText,
265 on_reasoning: &'a OnReasoning,
266 ) -> Pin<Box<dyn Future<Output = Result<CompletionResponse, Error>> + Send + 'a>> {
267 let _ = on_reasoning;
268 self.stream_complete(request, on_text)
269 }
270
271 fn model_name(&self) -> Option<&str>;
273}
274
275impl<P: LlmProvider> DynLlmProvider for P {
276 fn complete<'a>(
277 &'a self,
278 request: CompletionRequest,
279 ) -> Pin<Box<dyn Future<Output = Result<CompletionResponse, Error>> + Send + 'a>> {
280 Box::pin(LlmProvider::complete(self, request))
281 }
282
283 fn stream_complete<'a>(
284 &'a self,
285 request: CompletionRequest,
286 on_text: &'a OnText,
287 ) -> Pin<Box<dyn Future<Output = Result<CompletionResponse, Error>> + Send + 'a>> {
288 Box::pin(LlmProvider::stream_complete(self, request, on_text))
289 }
290
291 fn stream_complete_with_reasoning<'a>(
292 &'a self,
293 request: CompletionRequest,
294 on_text: &'a OnText,
295 on_reasoning: &'a OnReasoning,
296 ) -> Pin<Box<dyn Future<Output = Result<CompletionResponse, Error>> + Send + 'a>> {
297 Box::pin(LlmProvider::stream_complete_with_reasoning(
298 self,
299 request,
300 on_text,
301 on_reasoning,
302 ))
303 }
304
305 fn model_name(&self) -> Option<&str> {
306 LlmProvider::model_name(self)
307 }
308}
309
310pub struct BoxedProvider(Box<dyn DynLlmProvider>);
330
331impl BoxedProvider {
332 pub fn new<P: LlmProvider + 'static>(provider: P) -> Self {
334 Self(Box::new(provider))
335 }
336
337 pub fn from_arc<P: LlmProvider + 'static>(provider: Arc<P>) -> Self {
343 struct ArcAdapter<P>(Arc<P>);
345
346 impl<P: LlmProvider> LlmProvider for ArcAdapter<P> {
347 async fn complete(
348 &self,
349 request: CompletionRequest,
350 ) -> Result<CompletionResponse, Error> {
351 self.0.complete(request).await
352 }
353
354 async fn stream_complete(
355 &self,
356 request: CompletionRequest,
357 on_text: &OnText,
358 ) -> Result<CompletionResponse, Error> {
359 self.0.stream_complete(request, on_text).await
360 }
361
362 async fn stream_complete_with_reasoning(
363 &self,
364 request: CompletionRequest,
365 on_text: &OnText,
366 on_reasoning: &OnReasoning,
367 ) -> Result<CompletionResponse, Error> {
368 self.0
369 .stream_complete_with_reasoning(request, on_text, on_reasoning)
370 .await
371 }
372
373 fn model_name(&self) -> Option<&str> {
374 self.0.model_name()
375 }
376 }
377
378 Self(Box::new(ArcAdapter(provider)))
379 }
380}
381
382impl LlmProvider for BoxedProvider {
383 async fn complete(&self, request: CompletionRequest) -> Result<CompletionResponse, Error> {
384 self.0.complete(request).await
385 }
386
387 async fn stream_complete(
388 &self,
389 request: CompletionRequest,
390 on_text: &OnText,
391 ) -> Result<CompletionResponse, Error> {
392 self.0.stream_complete(request, on_text).await
393 }
394
395 async fn stream_complete_with_reasoning(
396 &self,
397 request: CompletionRequest,
398 on_text: &OnText,
399 on_reasoning: &OnReasoning,
400 ) -> Result<CompletionResponse, Error> {
401 self.0
402 .stream_complete_with_reasoning(request, on_text, on_reasoning)
403 .await
404 }
405
406 fn model_name(&self) -> Option<&str> {
407 self.0.model_name()
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use crate::llm::types::{ContentBlock, Message, StopReason, TokenUsage};
415 use std::sync::{Arc, Mutex};
416
417 struct FakeProvider;
418
419 impl LlmProvider for FakeProvider {
420 async fn complete(&self, _request: CompletionRequest) -> Result<CompletionResponse, Error> {
421 Ok(CompletionResponse {
422 content: vec![ContentBlock::Text {
423 text: "fake".into(),
424 }],
425 stop_reason: StopReason::EndTurn,
426 reasoning: None,
427 usage: TokenUsage::default(),
428 model: None,
429 })
430 }
431 }
432
433 struct StreamingFakeProvider;
434
435 impl LlmProvider for StreamingFakeProvider {
436 async fn complete(&self, _request: CompletionRequest) -> Result<CompletionResponse, Error> {
437 panic!("should call stream_complete, not complete");
438 }
439
440 async fn stream_complete(
441 &self,
442 _request: CompletionRequest,
443 on_text: &OnText,
444 ) -> Result<CompletionResponse, Error> {
445 on_text("hello");
446 on_text(" world");
447 Ok(CompletionResponse {
448 content: vec![ContentBlock::Text {
449 text: "hello world".into(),
450 }],
451 stop_reason: StopReason::EndTurn,
452 reasoning: None,
453 usage: TokenUsage::default(),
454 model: None,
455 })
456 }
457 }
458
459 fn test_request() -> CompletionRequest {
460 CompletionRequest {
461 system: String::new(),
462 messages: vec![Message::user("test")],
463 tools: vec![],
464 max_tokens: 100,
465 tool_choice: None,
466 reasoning_effort: None,
467 }
468 }
469
470 #[test]
471 fn dyn_llm_provider_wraps_provider() {
472 let provider = FakeProvider;
473 let dyn_provider: &dyn DynLlmProvider = &provider;
474 let _ = dyn_provider;
475 }
476
477 #[tokio::test]
478 async fn boxed_provider_delegates_complete() {
479 let provider = BoxedProvider::new(FakeProvider);
480 let response = LlmProvider::complete(&provider, test_request())
482 .await
483 .unwrap();
484 assert_eq!(response.text(), "fake");
485 }
486
487 #[tokio::test]
488 async fn boxed_provider_delegates_stream_complete() {
489 let provider = BoxedProvider::new(StreamingFakeProvider);
490 let received = Arc::new(Mutex::new(Vec::<String>::new()));
491 let received_clone = received.clone();
492 let on_text: &OnText = &move |text: &str| {
493 received_clone
494 .lock()
495 .expect("test lock")
496 .push(text.to_string());
497 };
498
499 let response = LlmProvider::stream_complete(&provider, test_request(), on_text)
500 .await
501 .unwrap();
502 assert_eq!(response.text(), "hello world");
503
504 let texts = received.lock().expect("test lock");
505 assert_eq!(*texts, vec!["hello", " world"]);
506 }
507
508 #[test]
509 fn boxed_provider_is_send_sync() {
510 fn assert_send_sync<T: Send + Sync>() {}
511 assert_send_sync::<BoxedProvider>();
512 }
513
514 #[tokio::test]
515 async fn boxed_provider_default_stream_falls_back_to_complete() {
516 let provider = BoxedProvider::new(FakeProvider);
518 let on_text: &OnText = &|_| {};
519 let response = LlmProvider::stream_complete(&provider, test_request(), on_text)
520 .await
521 .unwrap();
522 assert_eq!(response.text(), "fake");
523 }
524
525 #[tokio::test]
526 async fn boxed_provider_from_arc_delegates_complete() {
527 let provider = Arc::new(FakeProvider);
528 let boxed = BoxedProvider::from_arc(provider);
529 let response = LlmProvider::complete(&boxed, test_request()).await.unwrap();
530 assert_eq!(response.text(), "fake");
531 }
532
533 #[tokio::test]
534 async fn boxed_provider_from_arc_delegates_stream_complete() {
535 let provider = Arc::new(StreamingFakeProvider);
536 let boxed = BoxedProvider::from_arc(provider);
537 let received = Arc::new(Mutex::new(Vec::<String>::new()));
538 let received_clone = received.clone();
539 let on_text: &OnText = &move |text: &str| {
540 received_clone
541 .lock()
542 .expect("test lock")
543 .push(text.to_string());
544 };
545 let response = LlmProvider::stream_complete(&boxed, test_request(), on_text)
546 .await
547 .unwrap();
548 assert_eq!(response.text(), "hello world");
549 let texts = received.lock().expect("test lock");
550 assert_eq!(*texts, vec!["hello", " world"]);
551 }
552
553 #[test]
554 fn model_name_default_is_none() {
555 let provider = FakeProvider;
556 assert!(LlmProvider::model_name(&provider).is_none());
557 }
558
559 #[test]
560 fn boxed_provider_preserves_model_name() {
561 struct NamedProvider;
562 impl LlmProvider for NamedProvider {
563 async fn complete(
564 &self,
565 _request: CompletionRequest,
566 ) -> Result<CompletionResponse, Error> {
567 unimplemented!()
568 }
569 fn model_name(&self) -> Option<&str> {
570 Some("test-model")
571 }
572 }
573 let boxed = BoxedProvider::new(NamedProvider);
574 assert_eq!(LlmProvider::model_name(&boxed), Some("test-model"));
575 }
576
577 #[test]
578 fn boxed_provider_from_arc_preserves_model_name() {
579 struct NamedProvider;
580 impl LlmProvider for NamedProvider {
581 async fn complete(
582 &self,
583 _request: CompletionRequest,
584 ) -> Result<CompletionResponse, Error> {
585 unimplemented!()
586 }
587 fn model_name(&self) -> Option<&str> {
588 Some("arc-model")
589 }
590 }
591 let boxed = BoxedProvider::from_arc(Arc::new(NamedProvider));
592 assert_eq!(LlmProvider::model_name(&boxed), Some("arc-model"));
593 }
594
595 #[tokio::test]
596 async fn boxed_provider_from_arc_shares_underlying_provider() {
597 let call_count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
599 struct CountingProvider(Arc<std::sync::atomic::AtomicUsize>);
600 impl LlmProvider for CountingProvider {
601 async fn complete(
602 &self,
603 _request: CompletionRequest,
604 ) -> Result<CompletionResponse, crate::error::Error> {
605 self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
606 Ok(CompletionResponse {
607 content: vec![ContentBlock::Text {
608 text: "counted".into(),
609 }],
610 stop_reason: StopReason::EndTurn,
611 reasoning: None,
612 usage: TokenUsage::default(),
613 model: None,
614 })
615 }
616 }
617
618 let inner = Arc::new(CountingProvider(call_count.clone()));
619 let boxed1 = BoxedProvider::from_arc(inner.clone());
620 let boxed2 = BoxedProvider::from_arc(inner);
621
622 LlmProvider::complete(&boxed1, test_request())
623 .await
624 .unwrap();
625 LlmProvider::complete(&boxed2, test_request())
626 .await
627 .unwrap();
628
629 assert_eq!(
630 call_count.load(std::sync::atomic::Ordering::Relaxed),
631 2,
632 "both boxed providers should share the same underlying provider"
633 );
634 }
635
636 #[test]
639 fn approval_decision_from_true() {
640 let decision = ApprovalDecision::from(true);
641 assert_eq!(decision, ApprovalDecision::Allow);
642 assert!(decision.is_allowed());
643 assert!(!decision.is_persistent());
644 }
645
646 #[test]
647 fn approval_decision_from_false() {
648 let decision = ApprovalDecision::from(false);
649 assert_eq!(decision, ApprovalDecision::Deny);
650 assert!(!decision.is_allowed());
651 assert!(!decision.is_persistent());
652 }
653
654 #[test]
655 fn approval_decision_always_allow() {
656 let decision = ApprovalDecision::AlwaysAllow;
657 assert!(decision.is_allowed());
658 assert!(decision.is_persistent());
659 }
660
661 #[test]
662 fn approval_decision_always_deny() {
663 let decision = ApprovalDecision::AlwaysDeny;
664 assert!(!decision.is_allowed());
665 assert!(decision.is_persistent());
666 }
667}