diff --git a/src/google/adk/cli/cli.py b/src/google/adk/cli/cli.py index bac74558c94..09056d540a3 100644 --- a/src/google/adk/cli/cli.py +++ b/src/google/adk/cli/cli.py @@ -193,6 +193,19 @@ def _collect_pending_function_calls( return pending +def _enable_line_editing() -> None: + """Enables arrow-key editing and history for input() when available. + + input() only supports line editing once the readline module is imported. + readline is not available on Windows, where the console already provides + history. + """ + try: + import readline # noqa: F401 + except ImportError: + pass + + def _is_positive_response(s: str) -> bool: """Returns True if the string is a positive response.""" return s.strip().lower() in ('y', 'yes', 'true', 'confirm') @@ -267,6 +280,7 @@ async def run_interactively( agent_loader: Optional[AgentLoader] = None, agent_folder_name: Optional[str] = None, ) -> None: + _enable_line_editing() app = _to_app(root_agent_or_app, session.app_name) runner = Runner( app=app, diff --git a/tests/unittests/cli/utils/test_cli.py b/tests/unittests/cli/utils/test_cli.py index 8de9dc61445..6936c1b1da5 100644 --- a/tests/unittests/cli/utils/test_cli.py +++ b/tests/unittests/cli/utils/test_cli.py @@ -644,6 +644,53 @@ async def test_run_interactively_whitespace_and_exit( assert any("echo:hello" in m for m in echoed) +def test_enable_line_editing_imports_readline( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """_enable_line_editing should import readline so input() gets history.""" + pytest.importorskip("readline") + monkeypatch.delitem(sys.modules, "readline", raising=False) + + cli._enable_line_editing() + + assert "readline" in sys.modules + + +def test_enable_line_editing_without_readline( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """_enable_line_editing should not fail where readline is missing.""" + # A None entry in sys.modules makes the import raise ImportError, as on + # Windows. + monkeypatch.setitem(sys.modules, "readline", None) + + cli._enable_line_editing() + + +@pytest.mark.asyncio +async def test_run_interactively_enables_line_editing( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """run_interactively should enable line editing before reading input.""" + session_service = InMemorySessionService() + sess = await session_service.create_session(app_name="dummy", user_id="u") + calls: list[str] = [] + monkeypatch.setattr( + cli, "_enable_line_editing", lambda: calls.append("enabled") + ) + monkeypatch.setattr("builtins.input", lambda *_a, **_k: "exit") + + await cli.run_interactively( + BaseAgent(name="root"), + InMemoryArtifactService(), + sess, + session_service, + InMemoryCredentialService(), + ) + + assert calls == ["enabled"] + + def test_print_event_omits_internal_metadata_in_jsonl( monkeypatch: pytest.MonkeyPatch, ) -> None: