5858 from sse_starlette .sse import EventSourceResponse
5959 from starlette .applications import Starlette
6060 from starlette .authentication import BaseUser
61+ from starlette .datastructures import URL
6162 from starlette .exceptions import HTTPException
6263 from starlette .requests import Request
6364 from starlette .responses import JSONResponse , Response
@@ -485,6 +486,63 @@ async def event_generator(
485486 handler_result .root .model_dump (mode = 'json' , exclude_none = True ),
486487 headers = headers ,
487488 )
489+
490+ def _modify_rpc_url (self , agent_card : AgentCard , request : Request ):
491+ rpc_url = URL (agent_card .url )
492+ rpc_path = rpc_url .path
493+ port = None
494+ if "X-Forwarded-Host" in request .headers :
495+ host = request .headers ["X-Forwarded-Host" ]
496+ else :
497+ host = request .url .hostname
498+ port = request .url .port
499+
500+ if "X-Forwarded-Proto" in request .headers :
501+ scheme = request .headers ["X-Forwarded-Proto" ]
502+ else :
503+ scheme = request .url .scheme
504+ if not scheme :
505+ scheme = "http"
506+ if ":" in host : # type: ignore
507+ comps = host .rsplit (":" , 1 ) # type: ignore
508+ host = comps [0 ]
509+ port = comps [1 ]
510+
511+ # Handle URL maps,
512+ # e.g. "agents/my-agent/.well-known/agent-card.json"
513+ if "X-Forwarded-Path" in request .headers :
514+ forwarded_path = request .headers ["X-Forwarded-Path" ].strip ()
515+ if (
516+ forwarded_path and
517+ request .url .path != forwarded_path
518+ and forwarded_path .endswith (request .url .path )
519+ ):
520+ # "agents/my-agent" for "agents/my-agent/.well-known/agent-card.json"
521+ extra_path = forwarded_path [:- len (request .url .path )]
522+ new_path = extra_path + rpc_path
523+ # If original path was just "/",
524+ # we remove trailing "/" in the the extended one
525+ if len (new_path ) > 1 and rpc_path == "/" :
526+ new_path = new_path .rstrip ("/" )
527+ rpc_path = new_path
528+
529+ if port :
530+ agent_card .url = str (
531+ rpc_url .replace (
532+ hostname = host ,
533+ port = port ,
534+ scheme = scheme ,
535+ path = rpc_path
536+ )
537+ )
538+ else :
539+ agent_card .url = str (
540+ rpc_url .replace (
541+ hostname = host ,
542+ scheme = scheme ,
543+ path = rpc_path
544+ )
545+ )
488546
489547 async def _handle_get_agent_card (self , request : Request ) -> JSONResponse :
490548 """Handles GET requests for the agent card endpoint.
@@ -502,8 +560,12 @@ async def _handle_get_agent_card(self, request: Request) -> JSONResponse:
502560 )
503561
504562 card_to_serve = self .agent_card
563+ rpc_url = card_to_serve .url
505564 if self .card_modifier :
506565 card_to_serve = self .card_modifier (card_to_serve )
566+ # If agent's RPC URL was not modified, we build it dynamically.
567+ if rpc_url == card_to_serve .url :
568+ self ._modify_rpc_url (card_to_serve , request )
507569
508570 return JSONResponse (
509571 card_to_serve .model_dump (
@@ -528,13 +590,17 @@ async def _handle_get_authenticated_extended_agent_card(
528590
529591 card_to_serve = self .extended_agent_card
530592
593+ rpc_url = card_to_serve .url if card_to_serve else None
531594 if self .extended_card_modifier :
532595 context = self ._context_builder .build (request )
533596 # If no base extended card is provided, pass the public card to the modifier
534597 base_card = card_to_serve if card_to_serve else self .agent_card
535598 card_to_serve = self .extended_card_modifier (base_card , context )
536599
537600 if card_to_serve :
601+ # If agent's RPC URL was not modified, we build it dynamically.
602+ if rpc_url == card_to_serve .url :
603+ self ._modify_rpc_url (card_to_serve , request )
538604 return JSONResponse (
539605 card_to_serve .model_dump (
540606 exclude_none = True ,
0 commit comments