1313
1414from src .connectors .base import LLMBackend
1515from src .core .common .exceptions import (
16+ AuthenticationError ,
1617 BackendError ,
1718 ServiceUnavailableError ,
1819)
@@ -415,7 +416,7 @@ async def chat_completions( # type: ignore[override]
415416 effective_model : str ,
416417 identity : IAppIdentityConfig | None = None ,
417418 openrouter_api_base_url : str | None = None ,
418- openrouter_headers_provider : Callable [[str , str ], dict [str , str ]] | None = None ,
419+ openrouter_headers_provider : Callable [[Any , str ], dict [str , str ]] | None = None ,
419420 key_name : str | None = None ,
420421 api_key : str | None = None ,
421422 project : str | None = None ,
@@ -425,7 +426,12 @@ async def chat_completions( # type: ignore[override]
425426 ) -> ResponseEnvelope | StreamingResponseEnvelope :
426427 # Resolve base configuration
427428 base_api_url , headers = await self ._resolve_gemini_api_config (
428- gemini_api_base_url , openrouter_api_base_url , api_key , ** kwargs
429+ gemini_api_base_url ,
430+ openrouter_api_base_url ,
431+ api_key ,
432+ openrouter_headers_provider = openrouter_headers_provider ,
433+ key_name = key_name ,
434+ ** kwargs ,
429435 )
430436 if identity :
431437 headers .update (identity .get_resolved_headers (None ))
@@ -530,11 +536,31 @@ async def chat_completions( # type: ignore[override]
530536 model_url , payload , headers , effective_model
531537 )
532538
539+ def _build_openrouter_header_context (self ) -> dict [str , str ]:
540+ referer = "http://localhost:8000"
541+ title = "InterceptorProxy"
542+
543+ identity = getattr (self .config , "identity" , None )
544+ if identity is not None :
545+ referer = (
546+ getattr (getattr (identity , "url" , None ), "default_value" , referer )
547+ or referer
548+ )
549+ title = (
550+ getattr (getattr (identity , "title" , None ), "default_value" , title )
551+ or title
552+ )
553+
554+ return {"app_site_url" : referer , "app_x_title" : title }
555+
533556 async def _resolve_gemini_api_config (
534557 self ,
535558 gemini_api_base_url : str | None ,
536559 openrouter_api_base_url : str | None ,
537560 api_key : str | None ,
561+ * ,
562+ openrouter_headers_provider : Callable [[Any , str ], dict [str , str ]] | None = None ,
563+ key_name : str | None = None ,
538564 ** kwargs : Any ,
539565 ) -> tuple [str , dict [str , str ]]:
540566 # Prefer explicit params, then kwargs, then instance attributes set during initialize
@@ -550,12 +576,77 @@ async def _resolve_gemini_api_config(
550576 status_code = 500 ,
551577 detail = "Gemini API base URL and API key must be provided." ,
552578 )
553- key_name_to_use = (
554- kwargs .get ("key_name" )
555- or getattr (self , "key_name" , None )
556- or "x-goog-api-key"
579+ normalized_base = base .rstrip ("/" )
580+
581+ # Only use OpenRouter mode if the chosen base is actually OpenRouter
582+ # OpenRouter mode should only be enabled when the resolved base URL is different
583+ # from the default Gemini API base URL, indicating we're actually routing to OpenRouter
584+ gemini_default_base = "https://generativelanguage.googleapis.com"
585+ using_openrouter = (
586+ openrouter_api_base_url is not None
587+ and normalized_base != gemini_default_base .rstrip ("/" )
557588 )
558- return base .rstrip ("/" ), ensure_loop_guard_header ({key_name_to_use : key })
589+
590+ headers : dict [str , str ]
591+ if using_openrouter :
592+ headers = {}
593+ provided_headers : dict [str , str ] | None = None
594+
595+ if openrouter_headers_provider is not None :
596+ errors : list [Exception ] = []
597+
598+ if key_name is not None :
599+ try :
600+ candidate = openrouter_headers_provider (key_name , key )
601+ except (AttributeError , TypeError ) as exc :
602+ errors .append (exc )
603+ else :
604+ if candidate :
605+ provided_headers = dict (candidate )
606+
607+ if provided_headers is None :
608+ context = self ._build_openrouter_header_context ()
609+ try :
610+ candidate = openrouter_headers_provider (context , key )
611+ except Exception as exc : # pragma: no cover - defensive guard
612+ if errors and logger .isEnabledFor (logging .DEBUG ):
613+ logger .debug (
614+ "OpenRouter headers provider rejected key_name input: %s" ,
615+ errors [- 1 ],
616+ exc_info = True ,
617+ )
618+ raise AuthenticationError (
619+ message = "OpenRouter headers provider failed to produce headers." ,
620+ code = "missing_credentials" ,
621+ ) from exc
622+ else :
623+ provided_headers = dict (candidate )
624+
625+ if provided_headers is None :
626+ context = self ._build_openrouter_header_context ()
627+ provided_headers = {
628+ "Authorization" : f"Bearer { key } " ,
629+ "Content-Type" : "application/json" ,
630+ "HTTP-Referer" : context ["app_site_url" ],
631+ "X-Title" : context ["app_x_title" ],
632+ }
633+
634+ headers .update (provided_headers )
635+ context = self ._build_openrouter_header_context ()
636+ headers .setdefault ("Authorization" , f"Bearer { key } " )
637+ headers .setdefault ("Content-Type" , "application/json" )
638+ headers .setdefault ("HTTP-Referer" , context ["app_site_url" ])
639+ headers .setdefault ("X-Title" , context ["app_x_title" ])
640+ else :
641+ key_name_to_use = (
642+ key_name
643+ or kwargs .get ("key_name" )
644+ or getattr (self , "key_name" , None )
645+ or "x-goog-api-key"
646+ )
647+ headers = {key_name_to_use : key }
648+
649+ return normalized_base , ensure_loop_guard_header (headers )
559650
560651 def _apply_generation_config (
561652 self , payload : dict [str , Any ], request_data : ChatRequest
0 commit comments