⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/google/adk/cli/adk_web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class RunAgentRequest(common.BaseModel):
app_name: str
user_id: str
session_id: str
new_message: types.Content
new_message: Optional[types.Content] = None
streaming: bool = False
state_delta: Optional[dict[str, Any]] = None
# for resume long-running functions
Expand Down
21 changes: 15 additions & 6 deletions src/google/adk/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,7 @@ async def run_async(
The events generated by the agent.

Raises:
ValueError: If the session is not found; If both invocation_id and
new_message are None.
ValueError: If the session is not found and auto-creation is disabled.
"""
run_config = run_config or RunConfig()

Expand All @@ -497,12 +496,22 @@ async def _run_with_trace(
session = await self._get_or_create_session(
user_id=user_id, session_id=session_id
)

if not invocation_id and not new_message:
raise ValueError(
'Running an agent requires either a new_message or an '
'invocation_id to resume a previous invocation. '
f'Session: {session_id}, User: {user_id}'
if state_delta:
logger.warning(
'state_delta provided without new_message or invocation_id for '
'session %s. The state_delta will be ignored.',
session_id,
)
logger.info(
'Performing no-op resume for session %s: no new_message or '
'invocation_id.',
session_id,
)
# If nothing is provided, this is a no-op resume. We return early
# without yielding any events.
return

if invocation_id:
if (
Expand Down
24 changes: 24 additions & 0 deletions tests/unittests/cli/test_fast_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ async def dummy_run_async(
run_config: Optional[RunConfig] = None,
invocation_id: Optional[str] = None,
):

if not invocation_id and not new_message:
if state_delta:
logger.warning("state_delta ignored in no-op resume")
return

run_config = run_config or RunConfig()
yield _event_1()
await asyncio.sleep(0)
Expand Down Expand Up @@ -1411,5 +1417,23 @@ def test_builder_save_rejects_traversal(builder_test_client, tmp_path):
assert not (tmp_path / "app" / "tmp" / "escape.yaml").exists()


def test_agent_run_resume_without_message(test_app, create_test_session):
"""Test that /run allows resuming a session without providing a new message."""
info = create_test_session
url = "/run"
payload = {
"app_name": info["app_name"],
"user_id": info["user_id"],
"session_id": info["session_id"],
"streaming": False,
}

response = test_app.post(url, json=payload)

# Verify the web server and dummy runner work together to return success
assert response.status_code == 200
assert response.json() == []


if __name__ == "__main__":
pytest.main(["-xvs", __file__])