fix(slack): make video attachments available to agents (#45512)
This commit is contained in:
parent
197337cc47
commit
2a5dc0ef3d
6 changed files with 274 additions and 3 deletions
|
|
@ -20,6 +20,7 @@ from gateway.config import Platform, PlatformConfig
|
|||
from gateway.platforms.base import (
|
||||
MessageEvent,
|
||||
MessageType,
|
||||
SUPPORTED_VIDEO_TYPES,
|
||||
is_host_excluded_by_no_proxy,
|
||||
)
|
||||
|
||||
|
|
@ -120,6 +121,9 @@ def _redirect_cache(tmp_path, monkeypatch):
|
|||
monkeypatch.setattr(
|
||||
"gateway.platforms.base.DOCUMENT_CACHE_DIR", tmp_path / "doc_cache"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"gateway.platforms.base.VIDEO_CACHE_DIR", tmp_path / "video_cache"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -1443,6 +1447,84 @@ class TestIncomingDocumentHandling:
|
|||
msg_event = adapter.handle_message.call_args[0][0]
|
||||
assert msg_event.message_type == MessageType.PHOTO
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_video_attachment_cached(self, adapter):
|
||||
"""Video attachments should be downloaded into the video cache."""
|
||||
video_bytes = b"\x00\x00\x00\x18ftypmp42fake-mp4"
|
||||
|
||||
with patch.object(
|
||||
adapter, "_download_slack_file_bytes", new_callable=AsyncMock
|
||||
) as dl:
|
||||
dl.return_value = video_bytes
|
||||
event = self._make_event(
|
||||
text="what happens in this?",
|
||||
files=[
|
||||
{
|
||||
"mimetype": "video/mp4",
|
||||
"name": "clip.mp4",
|
||||
"url_private_download": "https://files.slack.com/clip.mp4",
|
||||
"size": len(video_bytes),
|
||||
}
|
||||
],
|
||||
)
|
||||
await adapter._handle_slack_message(event)
|
||||
|
||||
msg_event = adapter.handle_message.call_args[0][0]
|
||||
assert msg_event.message_type == MessageType.VIDEO
|
||||
assert len(msg_event.media_urls) == 1
|
||||
assert os.path.exists(msg_event.media_urls[0])
|
||||
assert msg_event.media_types == [SUPPORTED_VIDEO_TYPES[".mp4"]]
|
||||
dl.assert_awaited_once_with("https://files.slack.com/clip.mp4", team_id="")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_file_shared_video_fallback_fetches_file_info(self, adapter):
|
||||
"""file_shared-only video events should still reach the agent."""
|
||||
video_bytes = b"\x00\x00\x00\x18ftypmp42fake-mp4"
|
||||
adapter._app.client.files_info = AsyncMock(
|
||||
return_value={
|
||||
"ok": True,
|
||||
"file": {
|
||||
"id": "FVIDEO",
|
||||
"mimetype": "video/mp4",
|
||||
"name": "clip.mp4",
|
||||
"url_private_download": "https://files.slack.com/clip.mp4",
|
||||
"size": len(video_bytes),
|
||||
"user": "U_USER",
|
||||
"shares": {
|
||||
"private": {
|
||||
"D123": [
|
||||
{"ts": "1234567890.000001"},
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
adapter, "_download_slack_file_bytes", new_callable=AsyncMock
|
||||
) as dl,
|
||||
patch("asyncio.sleep", new_callable=AsyncMock),
|
||||
):
|
||||
dl.return_value = video_bytes
|
||||
await adapter._handle_slack_file_shared(
|
||||
{
|
||||
"type": "file_shared",
|
||||
"channel_id": "D123",
|
||||
"file_id": "FVIDEO",
|
||||
"user_id": "U_USER",
|
||||
"event_ts": "1234567890.000002",
|
||||
}
|
||||
)
|
||||
|
||||
adapter._app.client.files_info.assert_awaited_once_with(file="FVIDEO")
|
||||
msg_event = adapter.handle_message.call_args[0][0]
|
||||
assert msg_event.message_type == MessageType.VIDEO
|
||||
assert len(msg_event.media_urls) == 1
|
||||
assert os.path.exists(msg_event.media_urls[0])
|
||||
assert msg_event.media_types == [SUPPORTED_VIDEO_TYPES[".mp4"]]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_failure_is_surfaced_in_message_text(self, adapter):
|
||||
"""Attachment download failures (401/403/HTML-body/etc.) should be
|
||||
|
|
|
|||
50
tests/gateway/test_video_context_note.py
Normal file
50
tests/gateway/test_video_context_note.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""Tests for video attachment context notes in gateway turns."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from gateway.config import GatewayConfig, Platform
|
||||
from gateway.platforms.base import MessageEvent, MessageType
|
||||
from gateway.session import SessionSource
|
||||
|
||||
|
||||
def _make_runner() -> "GatewayRunner": # type: ignore[name-defined]
|
||||
from gateway.run import GatewayRunner
|
||||
|
||||
runner = GatewayRunner.__new__(GatewayRunner)
|
||||
runner.config = GatewayConfig()
|
||||
runner.adapters = {}
|
||||
runner._has_setup_skill = lambda: False
|
||||
return runner
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_video_attachment_adds_path_note_without_document_wording():
|
||||
from gateway.run import _build_media_placeholder
|
||||
|
||||
runner = _make_runner()
|
||||
source = SessionSource(platform=Platform.SLACK, chat_id="D123", chat_type="dm")
|
||||
event = MessageEvent(
|
||||
text="what happens here?",
|
||||
message_type=MessageType.VIDEO,
|
||||
source=source,
|
||||
media_urls=["/tmp/video_clip.mp4"],
|
||||
media_types=["video/mp4"],
|
||||
)
|
||||
|
||||
with patch(
|
||||
"tools.credential_files.to_agent_visible_cache_path",
|
||||
side_effect=lambda path: path,
|
||||
):
|
||||
result = await runner._prepare_inbound_message_text(
|
||||
event=event,
|
||||
source=source,
|
||||
history=[],
|
||||
)
|
||||
|
||||
assert "video attachment" in result
|
||||
assert "/tmp/video_clip.mp4" in result
|
||||
assert "video analysis or media tool" in result
|
||||
assert "The user sent a document" not in result
|
||||
assert _build_media_placeholder(event) == "[User sent a video: /tmp/video_clip.mp4]"
|
||||
|
|
@ -378,12 +378,14 @@ class TestCacheDirectoryMounts:
|
|||
hermes_home.mkdir()
|
||||
(hermes_home / "cache" / "documents").mkdir(parents=True)
|
||||
(hermes_home / "cache" / "audio").mkdir(parents=True)
|
||||
(hermes_home / "cache" / "videos").mkdir(parents=True)
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
mounts = get_cache_directory_mounts()
|
||||
paths = {m["container_path"] for m in mounts}
|
||||
assert "/root/.hermes/cache/documents" in paths
|
||||
assert "/root/.hermes/cache/audio" in paths
|
||||
assert "/root/.hermes/cache/videos" in paths
|
||||
|
||||
def test_skips_nonexistent_dirs(self, tmp_path, monkeypatch):
|
||||
"""Dirs that don't exist on disk are not returned."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue