1414
1515import json
1616from typing import AsyncGenerator , Literal , Optional
17- from pydantic import Field
1817
1918from a2a .client .base_client import BaseClient
2019import httpx
2120import requests
22- from a2a .client .auth import CredentialService
2321from a2a .types import AgentCard
2422from google .adk .agents .remote_a2a_agent import RemoteA2aAgent
2523
24+ from veadk .integrations .ve_identity .utils import generate_headers
25+ from veadk .utils .auth import VE_TIP_TOKEN_CREDENTIAL_KEY , VE_TIP_TOKEN_HEADER
2626from veadk .utils .logger import get_logger
2727from google .adk .utils .context_utils import Aclosing
2828from google .adk .events .event import Event
@@ -67,18 +67,20 @@ class RemoteVeAgent(RemoteA2aAgent):
6767 with a configured `base_url` is provided. If both are given, they must
6868 not conflict.
6969 auth_token (Optional[str]):
70- Optional authentication token used for secure access. If not provided,
71- the agent will be accessed without authentication.
70+ Optional authentication token used for secure access during initialization.
71+ If not provided, the agent will be accessed without authentication.
72+ Note: For runtime authentication, use the credential service in InvocationContext.
7273 auth_method (Literal["header", "querystring"] | None):
73- The method of attaching the authentication token.
74- - `"header"`: Token is passed via HTTP `Authorization` header.
75- - `"querystring"`: Token is passed as a query parameter.
74+ The method of attaching the authentication token at runtime.
75+ - `"header"`: Token is retrieved from credential service and passed via HTTP `Authorization` header.
76+ - `"querystring"`: Token is retrieved from credential service and passed as a query parameter.
77+ - `None`: No runtime authentication injection (default).
78+ The credential is loaded from `InvocationContext.credential_service` using the
79+ app_name and user_id from the context.
7680 httpx_client (Optional[httpx.AsyncClient]):
7781 An optional, pre-configured `httpx.AsyncClient` to use for communication.
7882 This allows for client sharing and advanced configurations (e.g., proxies).
7983 If its `base_url` is set, it will be used as the agent's location.
80- credential_service (Optional[CredentialService]):
81- Optional credential service for injecting auth token.
8284
8385 Raises:
8486 ValueError:
@@ -90,52 +92,43 @@ class RemoteVeAgent(RemoteA2aAgent):
9092
9193 Examples:
9294 ```python
93- # Example 1: Connect using a URL
95+ # Example 1: Connect using a URL (no authentication)
9496 agent = RemoteVeAgent(
9597 name="public_agent",
9698 url="https://vefaas.example.com/agents/public"
9799 )
98100
99- # Example 2: Using Bearer token in header
101+ # Example 2: Using static Bearer token in header for initialization
100102 agent = RemoteVeAgent(
101103 name="secured_agent",
102104 url="https://vefaas.example.com/agents/secure",
103105 auth_token="my_secret_token",
104106 auth_method="header"
105107 )
106108
107- # Example 3: Using a pre-configured httpx_client
109+ # Example 3: Using runtime authentication with credential service
110+ # The auth token will be automatically injected from InvocationContext.credential_service
111+ agent = RemoteVeAgent(
112+ name="dynamic_auth_agent",
113+ url="https://vefaas.example.com/agents/secure",
114+ auth_method="header" # Will load credential at runtime
115+ )
116+
117+ # Example 4: Using a pre-configured httpx_client
108118 import httpx
109119 client = httpx.AsyncClient(
110120 base_url="https://vefaas.example.com/agents/query",
111121 timeout=600
112122 )
113123 agent = RemoteVeAgent(
114124 name="query_agent",
115- auth_token="my_secret_token",
116- auth_method="querystring",
125+ auth_method="querystring", # Will load credential at runtime
117126 httpx_client=client
118127 )
119-
120- # Example 4: Using a credential service
121- from veadk.a2a.credentials import VeCredentialStore
122- credential_service = VeCredentialStore()
123- credential_service.set_credentials(
124- session_id="session_123",
125- security_scheme_name="inbound_auth",
126- credential="bearer_token_xyz"
127- )
128- agent = RemoteVeAgent(
129- name="secured_agent",
130- url="https://vefaas.example.com/agents/secure",
131- credential_service=credential_service
132- )
133128 ```
134129 """
135130
136- credential_service : Optional [CredentialService ] = Field (
137- None , description = "Optional credential service for injecting auth token."
138- )
131+ auth_method : Literal ["header" , "querystring" ] | None = None
139132
140133 def __init__ (
141134 self ,
@@ -144,7 +137,6 @@ def __init__(
144137 auth_token : Optional [str ] = None ,
145138 auth_method : Literal ["header" , "querystring" ] | None = None ,
146139 httpx_client : Optional [httpx .AsyncClient ] = None ,
147- credential_service : Optional [CredentialService ] = None ,
148140 ):
149141 # Determine the effective URL for the agent and handle conflicts.
150142 effective_url = url
@@ -225,9 +217,9 @@ def __init__(
225217 if not client_was_provided :
226218 self ._httpx_client_needs_cleanup = True
227219
228- # Set credential service if provided
229- if credential_service :
230- self .credential_service = credential_service
220+ # Set auth_method if provided
221+ if auth_method :
222+ self .auth_method = auth_method
231223
232224 async def _run_async_impl (
233225 self , ctx : InvocationContext
@@ -268,14 +260,17 @@ async def _inject_auth_token(self, ctx: InvocationContext) -> None:
268260 """Inject authentication token from credential service into the HTTP client.
269261
270262 This method retrieves the authentication token from the credential service
271- using the session ID and updates the HTTP client headers to include the
272- Bearer token for subsequent requests .
263+ in the InvocationContext and updates the HTTP client headers or query params
264+ based on the configured auth_method .
273265
274266 Args:
275- ctx: Invocation context containing session information
267+ ctx: Invocation context containing credential service and user information
276268 """
277- # Skip if no credential service configured
278- if not self .credential_service :
269+ # Skip if no credential service in context
270+ if not ctx .credential_service :
271+ logger .debug (
272+ "No credential service in InvocationContext, skipping auth token injection"
273+ )
279274 return
280275
281276 # Skip if client is not initialized or not a BaseClient
@@ -302,30 +297,64 @@ async def _inject_auth_token(self, ctx: InvocationContext) -> None:
302297 return
303298
304299 try :
305- from a2a . client import ClientCallContext
306-
307- # Get credentials from credential service using session ID
308- token = await self . credential_service . get_credentials (
309- security_scheme_name = "inbound_auth" ,
310- context = ClientCallContext (
311- state = { "userId" : ctx . user_id , "sessionId" : ctx . session . id }
312- ) ,
300+ from veadk . utils . auth import build_auth_config
301+ from google . adk . agents . callback_context import CallbackContext
302+
303+ # Inject TIP token via header
304+ workload_auth_config = build_auth_config (
305+ auth_method = "apikey" ,
306+ credential_key = VE_TIP_TOKEN_CREDENTIAL_KEY ,
307+ header_name = VE_TIP_TOKEN_HEADER ,
313308 )
314309
315- if not token :
316- return
310+ tip_credential = await ctx .credential_service .load_credential (
311+ auth_config = workload_auth_config ,
312+ callback_context = CallbackContext (ctx ),
313+ )
317314
318- # Add "Bearer " prefix if not already present
319- if not token .startswith ("Bearer " ):
320- token = f"Bearer { token } "
315+ if tip_credential :
316+ self ._a2a_client ._transport .httpx_client .headers .update (
317+ {VE_TIP_TOKEN_HEADER : tip_credential .api_key }
318+ )
319+ logger .debug (
320+ f"Injected TIP token via header for app={ ctx .app_name } , user={ ctx .user_id } "
321+ )
321322
322- # Update HTTP client headers
323- self ._a2a_client ._transport .httpx_client .headers .update (
324- {"Authorization" : token }
323+ # Build auth config based on auth_method
324+ auth_config = build_auth_config (
325+ credential_key = "inbound_auth" ,
326+ auth_method = self .auth_method or "header" ,
327+ header_scheme = "bearer" ,
325328 )
326- logger .debug (
327- f"Injected auth token for user { ctx .user_id } and session { ctx .session .id } "
329+
330+ # Load credential from credential service
331+ credential = await ctx .credential_service .load_credential (
332+ auth_config = auth_config ,
333+ callback_context = CallbackContext (ctx ),
328334 )
329335
336+ if not credential :
337+ logger .debug (
338+ f"No credential loaded, skipping auth token injection for app={ ctx .app_name } , user={ ctx .user_id } "
339+ )
340+ return
341+
342+ # Inject credential based on auth_method
343+ if self .auth_method == "querystring" :
344+ # Extract API key
345+ api_key = credential .api_key
346+ new_params = dict (self ._a2a_client ._transport .httpx_client .params )
347+ new_params .update ({"token" : api_key })
348+ self ._a2a_client ._transport .httpx_client .params = new_params
349+ logger .debug (
350+ f"Injected auth token via querystring for app={ ctx .app_name } , user={ ctx .user_id } "
351+ )
352+ else :
353+ if headers := generate_headers (credential ):
354+ self ._a2a_client ._transport .httpx_client .headers .update (headers )
355+ logger .debug (
356+ f"Injected auth token via header for app={ ctx .app_name } , user={ ctx .user_id } "
357+ )
358+
330359 except Exception as e :
331360 logger .warning (f"Failed to inject auth token: { e } " , exc_info = True )
0 commit comments