@@ -10,9 +10,9 @@ use std::time::{Duration, SystemTime};
1010
1111use async_trait:: async_trait;
1212use futures_util:: { StreamExt , stream} ;
13- use http:: StatusCode ;
13+ use http:: { Method , StatusCode } ;
1414use reqwest:: RequestBuilder ;
15- use reqwest:: header:: { HeaderMap , RETRY_AFTER } ;
15+ use reqwest:: header:: { CONTENT_LENGTH , CONTENT_TYPE , HeaderMap , RETRY_AFTER } ;
1616use serde_json:: { Map , Value } ;
1717use switchyard_protocol:: {
1818 LlmRequest , LlmResponse , LlmResponseChunk , LlmResponseStreamEvent , Metadata , ModelId , Request ,
@@ -146,49 +146,35 @@ impl TranslatingLlmClient {
146146 } )
147147 }
148148
149- /// Whether `model` has an Anthropic backend that supports token counting.
150- pub fn supports_count_tokens ( & self , model : & ModelId ) -> bool {
151- self . backend_for ( model, WireFormat :: AnthropicMessages )
152- . is_some ( )
153- }
154-
155- /// Counts input tokens with `model`'s Anthropic backend.
156- ///
157- /// Returns an error when the model has no Anthropic backend or the upstream
158- /// request fails or returns invalid JSON.
159- pub async fn count_tokens ( & self , model : & ModelId , request : Request ) -> Result < Value > {
160- let backend = self
161- . backend_for ( model, WireFormat :: AnthropicMessages )
162- . ok_or_else ( || LlmClientError :: Configuration {
163- message : format ! ( "model {model} has no Anthropic backend for count_tokens" ) ,
164- } ) ?;
165- let Request {
166- mut llm_request,
167- metadata,
168- ..
169- } = request;
170- llm_request. model = Some ( model. to_string ( ) ) ;
171- let http_response = self
172- . send_encoded (
173- backend,
174- WireFormat :: AnthropicMessages ,
175- llm_request,
176- metadata. as_ref ( ) ,
177- model,
178- UpstreamEndpoint :: CountTokens ,
179- )
180- . await ?;
181- let body = match http_response {
182- EncodedResponse :: Buffered { body, .. } => body,
183- EncodedResponse :: Streaming ( _) => {
184- return Err ( LlmClientError :: InvalidRequest {
185- message : "count_tokens does not support streaming requests" . to_string ( ) ,
186- } ) ;
187- }
149+ /// Forwards a provider-native request through `backend` without translation.
150+ pub async fn forward (
151+ & self ,
152+ backend : & Backend ,
153+ method : Method ,
154+ path_and_query : & str ,
155+ body : reqwest:: Body ,
156+ metadata : Option < & Metadata > ,
157+ ) -> Result < reqwest:: Response > {
158+ let client = if backend. is_forwarding_auth ( ) {
159+ & self . forward_auth_client
160+ } else {
161+ & self . client
188162 } ;
189- serde_json:: from_slice ( & body) . map_err ( |error| LlmClientError :: InvalidResponse {
190- source : Box :: new ( error) ,
191- } )
163+ let mut builder = client
164+ . request ( method, backend. forwarding_url ( path_and_query) )
165+ . body ( body) ;
166+ builder = forward_metadata_headers ( builder, metadata) ;
167+ if let Some ( headers) = metadata. and_then ( |metadata| metadata. http_headers . as_ref ( ) ) {
168+ for name in [ CONTENT_TYPE , CONTENT_LENGTH ] {
169+ if let Some ( value) = headers. get ( & name) {
170+ builder = builder. header ( name, value) ;
171+ }
172+ }
173+ }
174+ builder = backend. apply_forwarded_auth ( builder, metadata) ;
175+ builder = apply_extra_headers ( builder, backend) ;
176+ builder = backend. apply_auth ( builder) ;
177+ builder. send ( ) . await . map_err ( convert_reqwest_error)
192178 }
193179
194180 /// Encode `llm_request` for `wire_format`, POST it to `url` with the request's
@@ -198,18 +184,15 @@ impl TranslatingLlmClient {
198184 /// response is returned as soon as its successful headers arrive. A non-success
199185 /// status maps to a typed error — a 400 is classified as a context-window
200186 /// overflow via the backend's provider rules. Shared by
201- /// [`call_rewrite_model`](Self::call_rewrite_model) (which POSTs to the
202- /// backend's completion URL and decodes a response) and
203- /// [`count_tokens`](Self::count_tokens) (which POSTs to the `count_tokens`
204- /// URL and returns the raw JSON).
187+ /// [`call_rewrite_model`](Self::call_rewrite_model), which POSTs to the
188+ /// backend's completion URL and decodes a response.
205189 async fn send_encoded (
206190 & self ,
207191 backend : & Backend ,
208192 wire_format : WireFormat ,
209193 llm_request : LlmRequest ,
210194 metadata : Option < & Metadata > ,
211195 model : & ModelId ,
212- endpoint : UpstreamEndpoint ,
213196 ) -> Result < EncodedResponse > {
214197 let mut body = encode_request ( & llm_request, wire_format)
215198 . map_err ( |error| LlmClientError :: RequestEncoding ( error. to_string ( ) ) ) ?;
@@ -230,9 +213,8 @@ impl TranslatingLlmClient {
230213 if matches ! ( backend, Backend :: OpenAiChat ( _) ) {
231214 ensure_openai_stream_usage ( & mut body) ;
232215 }
233- let streaming = endpoint. allows_streaming ( )
234- && body. get ( "stream" ) . and_then ( Value :: as_bool) . unwrap_or ( false ) ;
235- let url = endpoint. url ( backend) ;
216+ let streaming = body. get ( "stream" ) . and_then ( Value :: as_bool) . unwrap_or ( false ) ;
217+ let url = backend. url ( ) ;
236218 record_gen_ai_request ( & url, model, streaming) ;
237219
238220 let max_retries = u64:: from ( backend. max_retries ( ) ) ;
@@ -426,7 +408,6 @@ impl TranslatingLlmClient {
426408 llm_request,
427409 metadata. as_ref ( ) ,
428410 & model_id,
429- UpstreamEndpoint :: Completion ,
430411 )
431412 . await ?;
432413
@@ -559,25 +540,6 @@ impl RoutedLlmClient for TranslatingLlmClient {
559540 }
560541}
561542
562- #[ derive( Clone , Copy ) ]
563- enum UpstreamEndpoint {
564- Completion ,
565- CountTokens ,
566- }
567-
568- impl UpstreamEndpoint {
569- fn url ( self , backend : & Backend ) -> String {
570- match self {
571- UpstreamEndpoint :: Completion => backend. url ( ) ,
572- UpstreamEndpoint :: CountTokens => backend. count_tokens_url ( ) ,
573- }
574- }
575-
576- fn allows_streaming ( self ) -> bool {
577- matches ! ( self , UpstreamEndpoint :: Completion )
578- }
579- }
580-
581543enum EncodedResponse {
582544 Buffered { status : u16 , body : Vec < u8 > } ,
583545 Streaming ( reqwest:: Response ) ,
0 commit comments