diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index b97932d042..0dc63140c8 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -1636,6 +1636,9 @@ async def run_agent_live( modalities: List[Literal["TEXT", "AUDIO"]] = Query( default=["AUDIO"] ), # Only allows "TEXT" or "AUDIO" + proactive_audio: bool = Query(default=False), + affective_dialog: bool = Query(default=False), + session_resumption: bool = Query(default=False), ) -> None: await websocket.accept() @@ -1652,7 +1655,18 @@ async def run_agent_live( async def forward_events(): runner = await self.get_runner_async(app_name) - run_config = RunConfig(response_modalities=modalities) + run_config = RunConfig( + response_modalities=modalities, + proactivity=( + types.ProactivityConfig(proactive_audio=proactive_audio) + if proactive_audio + else None + ), + enable_affective_dialog=affective_dialog or None, + session_resumption=( + types.SessionResumptionConfig() if session_resumption else None + ), + ) async with Aclosing( runner.run_live( session=session, diff --git a/tests/unittests/cli/test_fast_api.py b/tests/unittests/cli/test_fast_api.py index 0c69605349..8f186dbdd0 100755 --- a/tests/unittests/cli/test_fast_api.py +++ b/tests/unittests/cli/test_fast_api.py @@ -113,7 +113,12 @@ def _event_state_delta(state_delta: dict[str, Any]): # Define mocked async generator functions for the Runner -async def dummy_run_live(self, session, live_request_queue): +async def dummy_run_live( + self, + session, + live_request_queue, + run_config: Optional[RunConfig] = None, +): yield _event_1() await asyncio.sleep(0) @@ -1411,5 +1416,48 @@ def test_builder_save_rejects_traversal(builder_test_client, tmp_path): assert not (tmp_path / "app" / "tmp" / "escape.yaml").exists() +def test_run_live_accepts_new_config_options(test_app, test_session_info): + """Test that /run_live endpoint accepts proactive_audio, affective_dialog, and session_resumption query parameters.""" + # First, create a session to use + session_url = ( + f"/apps/{test_session_info['app_name']}/users/" + f"{test_session_info['user_id']}/sessions/{test_session_info['session_id']}" + ) + test_app.post(session_url, json={"state": {}}) + + # The actual websocket test would require more elaborate setup. + # Here we verify that the endpoint accepts the new query parameters + # by checking FastAPI's route signature. + from fastapi import Query as FastApiQuery + + # Find the /run_live route + run_live_route = None + for route in test_app.app.routes: + if hasattr(route, "path") and route.path == "/run_live": + run_live_route = route + break + + assert run_live_route is not None, "/run_live route not found" + + # Check that the endpoint function signature includes the new parameters + import inspect + + endpoint_fn = run_live_route.endpoint + sig = inspect.signature(endpoint_fn) + params = sig.parameters + + # Verify the new parameters exist + assert "proactive_audio" in params, "proactive_audio parameter not found" + assert "affective_dialog" in params, "affective_dialog parameter not found" + assert ( + "session_resumption" in params + ), "session_resumption parameter not found" + + # Verify they have default values of False + assert params["proactive_audio"].default.default is False + assert params["affective_dialog"].default.default is False + assert params["session_resumption"].default.default is False + + if __name__ == "__main__": pytest.main(["-xvs", __file__])