mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-01 08:33:14 +00:00
feat: add Slack image support with url_private auth and content-type validation [closes: OPE-23] (#1073)
* feat: add Slack image support with url_private auth and content-type validation * fix: simplify Slack image fetch by removing manual redirect logic * fix: use urlparse hostname check to resolve CodeQL URL sanitization warning * Update agent/utils/multimodal.py Co-authored-by: Brace Sproul <braceasproul@gmail.com> * fix: simplify host matching conditions in multimodal image fetching * reverting changes --------- Co-authored-by: Brace Sproul <braceasproul@gmail.com>
This commit is contained in:
parent
46d7ed9d43
commit
f79e824d8e
2 changed files with 41 additions and 2 deletions
|
|
@ -8,6 +8,7 @@ import mimetypes
|
|||
import os
|
||||
import re
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
from langchain_core.messages.content import create_image_block
|
||||
|
|
@ -44,7 +45,8 @@ async def fetch_image_block(
|
|||
try:
|
||||
logger.debug("Fetching image from %s", image_url)
|
||||
headers = None
|
||||
if "uploads.linear.app" in image_url:
|
||||
host = (urlparse(image_url).hostname or "").lower()
|
||||
if host == "uploads.linear.app" or host.endswith(".uploads.linear.app"):
|
||||
linear_api_key = os.environ.get("LINEAR_API_KEY", "")
|
||||
if linear_api_key:
|
||||
headers = {"Authorization": linear_api_key}
|
||||
|
|
@ -53,7 +55,16 @@ async def fetch_image_block(
|
|||
"LINEAR_API_KEY not set; cannot authenticate image fetch for %s",
|
||||
image_url,
|
||||
)
|
||||
response = await client.get(image_url, headers=headers)
|
||||
elif host == "files.slack.com" or host.endswith(".files.slack.com"):
|
||||
slack_bot_token = os.environ.get("SLACK_BOT_TOKEN", "")
|
||||
if slack_bot_token:
|
||||
headers = {"Authorization": f"Bearer {slack_bot_token}"}
|
||||
else:
|
||||
logger.warning(
|
||||
"SLACK_BOT_TOKEN not set; cannot authenticate image fetch for %s",
|
||||
image_url,
|
||||
)
|
||||
response = await client.get(image_url, headers=headers, follow_redirects=True)
|
||||
response.raise_for_status()
|
||||
content_type = response.headers.get("Content-Type", "").split(";")[0].strip()
|
||||
if not content_type:
|
||||
|
|
@ -66,6 +77,15 @@ async def fetch_image_block(
|
|||
return None
|
||||
content_type = guessed
|
||||
|
||||
supported_types = {"image/jpeg", "image/png", "image/gif", "image/webp"}
|
||||
if content_type not in supported_types:
|
||||
logger.warning(
|
||||
"Unsupported content type '%s' for %s; skipping image",
|
||||
content_type,
|
||||
image_url,
|
||||
)
|
||||
return None
|
||||
|
||||
encoded = base64.b64encode(response.content).decode("ascii")
|
||||
logger.info(
|
||||
"Fetched image %s (%s, %d bytes)",
|
||||
|
|
|
|||
|
|
@ -771,6 +771,25 @@ async def process_slack_mention(event_data: dict[str, Any], repo_config: dict[st
|
|||
)
|
||||
content_blocks: list[dict[str, Any]] = [create_text_block(prompt)]
|
||||
|
||||
image_urls = dedupe_urls(
|
||||
[url for msg in context_messages for url in extract_image_urls(msg.get("text", ""))]
|
||||
+ [
|
||||
f["url_private"]
|
||||
for msg in context_messages
|
||||
for f in msg.get("files", [])
|
||||
if isinstance(f, dict)
|
||||
and f.get("mimetype", "").startswith("image/")
|
||||
and f.get("url_private")
|
||||
]
|
||||
)
|
||||
if image_urls:
|
||||
logger.info("Preparing %d image(s) for Slack mention", len(image_urls))
|
||||
async with httpx.AsyncClient() as http_client:
|
||||
for image_url in image_urls:
|
||||
image_block = await fetch_image_block(image_url, http_client)
|
||||
if image_block:
|
||||
content_blocks.append(image_block)
|
||||
|
||||
configurable: dict[str, Any] = {
|
||||
"repo": repo_config,
|
||||
"slack_thread": {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue