From 9bf547647f9984a77afa1b1ca5c023c53668a88b Mon Sep 17 00:00:00 2001 From: Charles Cheng Date: Tue, 21 Jul 2026 23:03:05 +0800 Subject: [PATCH] fix: sanitize SlackRunner session IDs --- .../adk/integrations/slack/slack_runner.py | 18 ++++++++-- .../integrations/slack/test_slack_runner.py | 33 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/google/adk/integrations/slack/slack_runner.py b/src/google/adk/integrations/slack/slack_runner.py index 689700e3e31..22911ea11a7 100644 --- a/src/google/adk/integrations/slack/slack_runner.py +++ b/src/google/adk/integrations/slack/slack_runner.py @@ -15,6 +15,7 @@ from __future__ import annotations import logging +import re from typing import Any from google.adk.runners import Runner @@ -31,6 +32,13 @@ logger = logging.getLogger("google_adk." + __name__) +_SESSION_ID_UNSAFE_CHARS = re.compile(r"[^A-Za-z0-9_-]") + + +def _to_session_id_part(value: str) -> str: + """Converts Slack identifiers into ADK session-id-safe strings.""" + return _SESSION_ID_UNSAFE_CHARS.sub("_", value) + class SlackRunner: """Runner for ADK agents on Slack.""" @@ -73,8 +81,14 @@ async def _handle_message(self, event: dict[str, Any], say: Any) -> None: if not text or not user_id or not channel_id: return - # In Slack, we can use the channel_id (and optionally thread_ts) as a session ID. - session_id = f"{channel_id}-{thread_ts}" if thread_ts else channel_id + # In Slack, we can use the channel_id (and optionally thread_ts) as a + # session ID. Slack timestamps contain "." but managed session services only + # accept letters, numbers, "_" and "-". + session_id = ( + f"{_to_session_id_part(channel_id)}-{_to_session_id_part(thread_ts)}" + if thread_ts + else _to_session_id_part(channel_id) + ) new_message = types.Content(role="user", parts=[types.Part(text=text)]) diff --git a/tests/unittests/integrations/slack/test_slack_runner.py b/tests/unittests/integrations/slack/test_slack_runner.py index bd2daa7d85f..3139f8f8ff8 100644 --- a/tests/unittests/integrations/slack/test_slack_runner.py +++ b/tests/unittests/integrations/slack/test_slack_runner.py @@ -74,6 +74,39 @@ async def mock_run_async(*args, **kwargs): text="Hi user!", ) + @patch("google.adk.integrations.slack.slack_runner.logger") + async def test_handle_message_sanitizes_slack_thread_ts_session_id( + self, mock_logger + ): + mock_say = AsyncMock() + mock_say.return_value = {"ts": "thinking_ts"} + event = { + "text": "Hello bot", + "user": "U12345", + "channel": "C67890", + "thread_ts": "1234567890.123456", + "ts": "1234567899.999999", + } + + async def mock_run_async(*args, **kwargs): + return + yield + + self.mock_runner.run_async.side_effect = mock_run_async + + await self.slack_runner._handle_message(event, mock_say) + + self.mock_runner.run_async.assert_called_once_with( + user_id="U12345", + session_id="C67890-1234567890_123456", + new_message=types.Content( + role="user", parts=[types.Part(text="Hello bot")] + ), + ) + mock_say.assert_called_once_with( + text="_Thinking..._", thread_ts="1234567890.123456" + ) + @patch("google.adk.integrations.slack.slack_runner.logger") async def test_handle_message_multi_turn(self, mock_logger): # Setup mocks