1pub mod breaker;
2pub mod cache;
3pub mod client;
4pub mod schema;
5pub mod shim;
6pub use llmshim_catalog as catalog;
8pub mod config;
9pub mod cost;
10pub mod credentials;
11mod default_secret_file;
12mod derived_response;
13pub mod env;
14pub mod error;
15pub mod fallback;
16mod json_bounds;
17pub mod log;
18pub mod models;
19pub mod policy;
20pub mod provider;
21pub mod providers;
22pub mod reasoning;
23#[cfg(feature = "redis-coordination")]
24mod redis_operation;
25pub mod router;
26mod sse;
27mod stream_retention;
28pub mod streaming;
29pub mod toolcall;
30pub mod usage;
31pub mod vision;
32
33#[cfg(feature = "proxy")]
34pub mod proxy;
35
36#[cfg(feature = "gateway")]
37pub mod gateway;
38
39use client::ShimClient;
40use error::Result;
41pub use fallback::{completion_with_fallback, completion_with_fallback_and_policy, FallbackConfig};
42use log::{LogEntry, Logger, RequestTimer};
43use policy::DispatchPolicyContext;
44use router::Router;
45use serde_json::Value;
46
47use futures::Stream;
48use std::pin::Pin;
49use std::sync::LazyLock;
50
51pub static SHARED_CLIENT: LazyLock<ShimClient> = LazyLock::new(ShimClient::new);
54
55pub async fn warmup(router: &Router) {
58 let urls: Vec<&str> = router
59 .provider_keys()
60 .iter()
61 .filter_map(|name| match *name {
62 "openai" => Some("https://api.openai.com"),
63 "anthropic" => Some("https://api.anthropic.com"),
64 "gemini" => Some("https://generativelanguage.googleapis.com"),
65 "xai" => Some("https://api.x.ai"),
66 _ => None,
67 })
68 .collect();
69 SHARED_CLIENT.warmup(&urls).await;
70}
71
72pub async fn completion(router: &Router, request: &Value) -> Result<Value> {
74 completion_with_logger(router, request, None).await
75}
76
77pub async fn completion_with_policy(
78 router: &Router,
79 request: &Value,
80 policy_context: &DispatchPolicyContext,
81) -> Result<Value> {
82 completion_with_logger_and_policy(router, request, None, policy_context).await
83}
84
85pub async fn completion_with_logger(
87 router: &Router,
88 request: &Value,
89 logger: Option<&Logger>,
90) -> Result<Value> {
91 completion_inner(router, request, logger, None).await
92}
93
94pub async fn completion_with_logger_and_policy(
95 router: &Router,
96 request: &Value,
97 logger: Option<&Logger>,
98 policy_context: &DispatchPolicyContext,
99) -> Result<Value> {
100 completion_inner(router, request, logger, Some(policy_context)).await
101}
102
103async fn completion_inner(
104 router: &Router,
105 request: &Value,
106 logger: Option<&Logger>,
107 policy_context: Option<&DispatchPolicyContext>,
108) -> Result<Value> {
109 let request = router.expand_route(request)?;
111 let request = request.as_ref();
112 let model_str = request
113 .get("model")
114 .and_then(|m| m.as_str())
115 .ok_or(error::ShimError::MissingModel)?;
116
117 let (provider, model) = router.resolve(model_str)?;
118 let client = bound_client(router);
119 let timer = RequestTimer::start();
120
121 let result = match policy_context {
125 Some(context) => {
126 client
127 .completion_with_policy(provider, &model, request, context)
128 .await
129 }
130 None => client.completion(provider, &model, request).await,
131 };
132
133 match result {
134 Ok(resp) => {
135 if let Some(logger) = logger {
136 logger.log(&LogEntry::from_response(
137 provider.name(),
138 model_str,
139 &resp,
140 timer.elapsed(),
141 ));
142 }
143 Ok(resp)
144 }
145 Err(e) => {
146 if let Some(logger) = logger {
147 logger.log(&LogEntry::from_error(
148 provider.name(),
149 model_str,
150 &e.to_string(),
151 timer.elapsed(),
152 ));
153 }
154 Err(e)
155 }
156 }
157}
158
159pub async fn stream(
161 router: &Router,
162 request: &Value,
163) -> Result<Pin<Box<dyn Stream<Item = Result<String>> + Send>>> {
164 stream_inner(router, request, None).await
165}
166
167pub async fn stream_with_policy(
168 router: &Router,
169 request: &Value,
170 policy_context: &DispatchPolicyContext,
171) -> Result<Pin<Box<dyn Stream<Item = Result<String>> + Send>>> {
172 stream_inner(router, request, Some(policy_context)).await
173}
174
175async fn stream_inner(
176 router: &Router,
177 request: &Value,
178 policy_context: Option<&DispatchPolicyContext>,
179) -> Result<Pin<Box<dyn Stream<Item = Result<String>> + Send>>> {
180 let request = router.expand_route(request)?;
181 let request = request.as_ref();
182 let model_str = request
183 .get("model")
184 .and_then(|m| m.as_str())
185 .ok_or(error::ShimError::MissingModel)?;
186
187 let (provider, model) = router.resolve_owned(model_str)?;
188 match policy_context {
191 Some(context) => {
192 bound_client(router)
193 .stream_owned_with_policy(provider, &model, request, context)
194 .await
195 }
196 None => {
197 bound_client(router)
198 .stream_owned(provider, &model, request)
199 .await
200 }
201 }
202}
203
204pub(crate) fn bound_client(router: &Router) -> ShimClient {
207 SHARED_CLIENT.clone().with_breaker(router.breaker().clone())
208}