mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-22 10:32:00 +00:00
feat(teams): native send_video/send_voice/send_document attachments (#49308)
Teams overrode send_image/send_image_file but not send_video, send_voice, or send_document — so when the gateway dispatched a video/voice/document reply to a Teams chat it fell through to the base-class text fallback and sent the local file path as plain text (same broken-UX class as the LINE URL-image gap in #49298). Extract the existing send_image attachment logic into a shared _send_media_attachment helper (remote URL by reference, local file as a base64 data URI, MIME guessed from the path) and route all four media kinds through it. 5 new tests cover remote-URL, local-file base64, no-app, and missing-file paths.
This commit is contained in:
parent
1e40b21b2e
commit
5f55f0ff85
2 changed files with 144 additions and 10 deletions
|
|
@ -1189,14 +1189,22 @@ class TeamsAdapter(BasePlatformAdapter):
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
async def send_image(
|
||||
async def _send_media_attachment(
|
||||
self,
|
||||
chat_id: str,
|
||||
image_url: str,
|
||||
source: str,
|
||||
default_mime: str,
|
||||
caption: Optional[str] = None,
|
||||
reply_to: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
media_label: str = "media",
|
||||
) -> SendResult:
|
||||
"""Send any media file/URL as a Teams attachment.
|
||||
|
||||
Remote ``http(s)://`` URLs are attached by reference; local paths
|
||||
(with optional ``file://`` prefix) are base64-encoded into a data
|
||||
URI. MIME type is guessed from the path/extension, falling back to
|
||||
``default_mime``. Shared by send_image / send_video / send_voice /
|
||||
send_document so every media kind uses the same Attachment path.
|
||||
"""
|
||||
if not self._app:
|
||||
return SendResult(success=False, error="Teams app not initialized")
|
||||
|
||||
|
|
@ -1205,13 +1213,13 @@ class TeamsAdapter(BasePlatformAdapter):
|
|||
import mimetypes
|
||||
from microsoft_teams.api import Attachment, MessageActivityInput
|
||||
|
||||
if image_url.startswith("http://") or image_url.startswith("https://"):
|
||||
content_url = image_url
|
||||
mime_type = "image/png"
|
||||
if source.startswith("http://") or source.startswith("https://"):
|
||||
content_url = source
|
||||
mime_type = mimetypes.guess_type(source.split("?")[0])[0] or default_mime
|
||||
else:
|
||||
# Local path — encode as base64 data URI
|
||||
path = image_url.removeprefix("file://")
|
||||
mime_type = mimetypes.guess_type(path)[0] or "image/png"
|
||||
path = source.removeprefix("file://")
|
||||
mime_type = mimetypes.guess_type(path)[0] or default_mime
|
||||
with open(path, "rb") as f:
|
||||
content_url = f"data:{mime_type};base64,{base64.b64encode(f.read()).decode()}"
|
||||
|
||||
|
|
@ -1228,9 +1236,25 @@ class TeamsAdapter(BasePlatformAdapter):
|
|||
|
||||
return SendResult(success=True, message_id=getattr(result, "id", None))
|
||||
except Exception as e:
|
||||
logger.error("[teams] send_image failed: %s", e, exc_info=True)
|
||||
logger.error("[teams] send_%s failed: %s", media_label, e, exc_info=True)
|
||||
return SendResult(success=False, error=str(e), retryable=True)
|
||||
|
||||
async def send_image(
|
||||
self,
|
||||
chat_id: str,
|
||||
image_url: str,
|
||||
caption: Optional[str] = None,
|
||||
reply_to: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> SendResult:
|
||||
return await self._send_media_attachment(
|
||||
chat_id=chat_id,
|
||||
source=image_url,
|
||||
default_mime="image/png",
|
||||
caption=caption,
|
||||
media_label="image",
|
||||
)
|
||||
|
||||
async def send_image_file(
|
||||
self,
|
||||
chat_id: str,
|
||||
|
|
@ -1246,6 +1270,58 @@ class TeamsAdapter(BasePlatformAdapter):
|
|||
reply_to=reply_to,
|
||||
)
|
||||
|
||||
async def send_video(
|
||||
self,
|
||||
chat_id: str,
|
||||
video_path: str,
|
||||
caption: Optional[str] = None,
|
||||
reply_to: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> SendResult:
|
||||
return await self._send_media_attachment(
|
||||
chat_id=chat_id,
|
||||
source=video_path,
|
||||
default_mime="video/mp4",
|
||||
caption=caption,
|
||||
media_label="video",
|
||||
)
|
||||
|
||||
async def send_voice(
|
||||
self,
|
||||
chat_id: str,
|
||||
audio_path: str,
|
||||
caption: Optional[str] = None,
|
||||
reply_to: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> SendResult:
|
||||
return await self._send_media_attachment(
|
||||
chat_id=chat_id,
|
||||
source=audio_path,
|
||||
default_mime="audio/mpeg",
|
||||
caption=caption,
|
||||
media_label="voice",
|
||||
)
|
||||
|
||||
async def send_document(
|
||||
self,
|
||||
chat_id: str,
|
||||
file_path: str,
|
||||
caption: Optional[str] = None,
|
||||
file_name: Optional[str] = None,
|
||||
reply_to: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> SendResult:
|
||||
return await self._send_media_attachment(
|
||||
chat_id=chat_id,
|
||||
source=file_path,
|
||||
default_mime="application/octet-stream",
|
||||
caption=caption,
|
||||
media_label="document",
|
||||
)
|
||||
|
||||
async def get_chat_info(self, chat_id: str) -> dict:
|
||||
return {"name": chat_id, "type": "unknown", "chat_id": chat_id}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue