From 05cf544e81da4bb4498c9e9f5f58137e67370398 Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 9 Sep 2026 11:47:38 +0200 Subject: [PATCH] fix(sessions): raise InvocationNotFoundError instead of bare ValueError on rewind rewind_session raised a bare ValueError with a hardcoded message when rewind_before_invocation_id matched no event in the session. Callers had no stable way to distinguish it from other failures short of matching that exact string, which a future wording change could break silently. Add InvocationNotFoundError (a ValueError subclass, for backward compatibility, mirroring SessionNotFoundError) and raise it instead so callers can catch it by type. --- .../adk/errors/invocation_not_found_error.py | 25 +++++++++++++++++++ src/google/adk/runners.py | 7 +++++- src/google/adk/sessions/_rewind_utils.py | 5 +++- tests/unittests/sessions/test_rewind_utils.py | 5 ++-- 4 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 src/google/adk/errors/invocation_not_found_error.py diff --git a/src/google/adk/errors/invocation_not_found_error.py b/src/google/adk/errors/invocation_not_found_error.py new file mode 100644 index 00000000000..c9dd2ff06aa --- /dev/null +++ b/src/google/adk/errors/invocation_not_found_error.py @@ -0,0 +1,25 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + + +class InvocationNotFoundError(ValueError): + """Raised when an invocation id cannot be found in the session's events. + + Inherits from ValueError (for backward compatibility). + """ + + def __init__(self, message: str = "Invocation ID not found.") -> None: + super().__init__(message) diff --git a/src/google/adk/runners.py b/src/google/adk/runners.py index 6816af0c0d8..91d923c9ff0 100644 --- a/src/google/adk/runners.py +++ b/src/google/adk/runners.py @@ -1292,7 +1292,12 @@ async def rewind_async( rewind_before_invocation_id: str, run_config: Optional[RunConfig] = None, ) -> None: - """Rewinds the session to before the specified invocation.""" + """Rewinds the session to before the specified invocation. + + Raises: + InvocationNotFoundError: If rewind_before_invocation_id does not match + any event in the session. + """ run_config = run_config or RunConfig() session = await self._get_or_create_session( user_id=user_id, diff --git a/src/google/adk/sessions/_rewind_utils.py b/src/google/adk/sessions/_rewind_utils.py index 233b9e4fe91..e45ca562598 100644 --- a/src/google/adk/sessions/_rewind_utils.py +++ b/src/google/adk/sessions/_rewind_utils.py @@ -25,6 +25,7 @@ from google.genai import types +from ..errors.invocation_not_found_error import InvocationNotFoundError from ..events.event import Event from ..events.event_actions import EventActions from ..platform import uuid as platform_uuid @@ -168,7 +169,9 @@ async def rewind_session( break if rewind_event_index == -1: - raise ValueError(f"Invocation ID not found: {rewind_before_invocation_id}") + raise InvocationNotFoundError( + f"Invocation ID not found: {rewind_before_invocation_id}" + ) # Compute state delta to reverse changes if compute_state_delta is not None: diff --git a/tests/unittests/sessions/test_rewind_utils.py b/tests/unittests/sessions/test_rewind_utils.py index 0031866b1d6..422e62084da 100644 --- a/tests/unittests/sessions/test_rewind_utils.py +++ b/tests/unittests/sessions/test_rewind_utils.py @@ -16,6 +16,7 @@ from __future__ import annotations +from google.adk.errors.invocation_not_found_error import InvocationNotFoundError from google.adk.events.event import Event from google.adk.events.event_actions import EventActions from google.adk.sessions import _rewind_utils @@ -58,13 +59,13 @@ async def test_compute_artifact_delta_returns_empty_when_no_artifact_service(): async def test_rewind_session_raises_when_invocation_not_found(): - """Rewinding to an invocation id not present in session raises ValueError.""" + """Rewinding to an invocation id not present in session raises InvocationNotFoundError.""" session_service = InMemorySessionService() session = await session_service.create_session( app_name="app", user_id="u1", session_id="s1" ) - with pytest.raises(ValueError, match="Invocation ID not found"): + with pytest.raises(InvocationNotFoundError, match="Invocation ID not found"): await _rewind_utils.rewind_session( session_service=session_service, session=session,