import asyncio from dotenv import load_dotenv from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService from google.genai import types from .helpers import is_pending_auth_event, get_function_call_id, get_function_call_auth_config, get_user_input from .tools_and_agent import root_agent load_dotenv() agent = root_agent async def async_main(): """ Main asynchronous function orchestrating the agent interaction and authentication flow. """ # --- Step 1: Service Initialization --- # Use in-memory services for session and artifact storage (suitable for demos/testing). session_service = InMemorySessionService() artifacts_service = InMemoryArtifactService() # Create a new user session to maintain conversation state. session = session_service.create_session( state={}, # Optional state dictionary for session-specific data app_name='my_app', # Application identifier user_id='user' # User identifier ) # --- Step 2: Initial User Query --- # Define the user's initial request. query = 'Show me my user info' print(f"user: {query}") # Format the query into the Content structure expected by the ADK Runner. content = types.Content(role='user', parts=[types.Part(text=query)]) # Initialize the ADK Runner runner = Runner( app_name='my_app', agent=agent, artifact_service=artifacts_service, session_service=session_service, ) # --- Step 3: Send Query and Handle Potential Auth Request --- print("\nRunning agent with initial query...") events_async = runner.run_async( session_id=session.id, user_id='user', new_message=content ) # Variables to store details if an authentication request occurs. auth_request_event_id, auth_config = None, None # Iterate through the events generated by the first run. async for event in events_async: # Check if this event is the specific 'adk_request_credential' function call. if is_pending_auth_event(event): print("--> Authentication required by agent.") auth_request_event_id = get_function_call_id(event) auth_config = get_function_call_auth_config(event) # Once the auth request is found and processed, exit this loop. # We need to pause execution here to get user input for authentication. break # If no authentication request was detected after processing all events, exit. if not auth_request_event_id or not auth_config: print("\nAuthentication not required for this query or processing finished.") return # Exit the main function # --- Step 4: Manual Authentication Step (Simulated OAuth 2.0 Flow) --- # This section simulates the user interaction part of an OAuth 2.0 flow. # In a real web application, this would involve browser redirects. # Define the Redirect URI. This *must* match one of the URIs registered # with the OAuth provider for your application. The provider sends the user # back here after they approve the request. redirect_uri = 'http://localhost:8000/dev-ui' # Example for local development # Construct the Authorization URL that the user must visit. # This typically includes the provider's authorization endpoint URL, # client ID, requested scopes, response type (e.g., 'code'), and the redirect URI. # Here, we retrieve the base authorization URI from the AuthConfig provided by ADK # and append the redirect_uri. # NOTE: A robust implementation would use urlencode and potentially add state, scope, etc. auth_request_uri = ( auth_config.exchanged_auth_credential.oauth2.auth_uri + f'&redirect_uri={redirect_uri}' # Simple concatenation; ensure correct query param format ) print("\n--- User Action Required ---") # Prompt the user to visit the authorization URL, log in, grant permissions, # and then paste the *full* URL they are redirected back to (which contains the auth code). auth_response_uri = await get_user_input( f'1. Please open this URL in your browser to log in:\n {auth_request_uri}\n\n' f'2. After successful login and authorization, your browser will be redirected.\n' f' Copy the *entire* URL from the browser\'s address bar.\n\n' f'3. Paste the copied URL here and press Enter:\n\n> ' ) # --- Step 5: Prepare Authentication Response for the Agent --- # Update the AuthConfig object with the information gathered from the user. # The ADK framework needs the full response URI (containing the code) # and the original redirect URI to complete the OAuth token exchange process internally. auth_config.exchanged_auth_credential.oauth2.auth_response_uri = auth_response_uri auth_config.exchanged_auth_credential.oauth2.redirect_uri = redirect_uri # Construct a FunctionResponse Content object to send back to the agent/runner. # This response explicitly targets the 'adk_request_credential' function call # identified earlier by its ID. auth_content = types.Content( role='user', parts=[ types.Part( function_response=types.FunctionResponse( # Crucially, link this response to the original request using the saved ID. id=auth_request_event_id, # The special name of the function call we are responding to. name='adk_request_credential', # The payload containing all necessary authentication details. response=auth_config.model_dump(), ) ) ], ) # --- Step 6: Resume Execution with Authentication --- print("\nSubmitting authentication details back to the agent...") # Run the agent again, this time providing the `auth_content` (FunctionResponse). # The ADK Runner intercepts this, processes the 'adk_request_credential' response # (performs token exchange, stores credentials), and then allows the agent # to retry the original tool call that required authentication, now succeeding with # a valid access token embedded. events_async = runner.run_async( session_id=session.id, user_id='user', new_message=auth_content, # Provide the prepared auth response ) # Process and print the final events from the agent after authentication is complete. # This stream now contain the actual result from the tool (e.g., the user info). print("\n--- Agent Response after Authentication ---") async for event in events_async: print(event) if __name__ == '__main__': asyncio.run(async_main())