refactor: keep image URLs in text and centralize image auth

This commit is contained in:
aran-yogesh 2026-02-22 18:57:49 -08:00
parent bec99b106e
commit 446b659c54
3 changed files with 40 additions and 56 deletions

View file

@ -27,6 +27,28 @@ class LinearNotifyState(AgentState):
linear_messages_sent_count: int
async def _build_blocks_from_payload(
payload: dict[str, Any],
) -> list[dict[str, Any]]:
text = payload.get("text", "")
image_urls = payload.get("image_urls", []) or []
blocks: list[dict[str, Any]] = []
if text:
blocks.append({"type": "text", "text": text})
if not image_urls:
return blocks
linear_api_key = os.environ.get("LINEAR_API_KEY", "")
async with httpx.AsyncClient() as client:
for image_url in image_urls:
image_block = await fetch_image_block(
image_url, client, linear_api_key=linear_api_key
)
if image_block:
blocks.append(image_block)
return blocks
@before_model(state_schema=LinearNotifyState)
async def check_message_queue_before_model( # noqa: PLR0911
state: LinearNotifyState, # noqa: ARG001
@ -84,36 +106,6 @@ async def check_message_queue_before_model( # noqa: PLR0911
thread_id,
)
async def _build_blocks_from_payload(
payload: dict[str, Any],
) -> list[dict[str, Any]]:
text = payload.get("text", "")
image_urls = payload.get("image_urls", []) or []
blocks: list[dict[str, Any]] = []
if text:
blocks.append({"type": "text", "text": text})
if not image_urls:
return blocks
linear_api_key = os.environ.get("LINEAR_API_KEY", "")
async with httpx.AsyncClient() as client:
for image_url in image_urls:
headers = None
if "uploads.linear.app" in image_url:
if linear_api_key:
headers = {"Authorization": linear_api_key}
else:
logger.warning(
"LINEAR_API_KEY not set; cannot authenticate image fetch for %s",
image_url,
)
image_block = await fetch_image_block(
image_url, client, headers=headers
)
if image_block:
blocks.append(image_block)
return blocks
content_blocks: list[dict[str, Any]] = []
for msg in queued_messages:
content = msg.get("content")

View file

@ -5,6 +5,7 @@ from __future__ import annotations
import base64
import logging
import mimetypes
import os
import re
from typing import Any
@ -35,26 +36,27 @@ def extract_image_urls(text: str) -> list[str]:
return deduped
def strip_image_urls(text: str, image_urls: list[str]) -> str:
"""Remove markdown image links and direct image URLs from text."""
if not text or not image_urls:
return text
logger.debug("Stripping %d image URL(s) from text", len(image_urls))
text = IMAGE_MARKDOWN_RE.sub("", text)
for url in image_urls:
text = text.replace(url, "")
return text
async def fetch_image_block(
image_url: str,
client: httpx.AsyncClient,
headers: dict[str, str] | None = None,
*,
linear_api_key: str | None = None,
) -> dict[str, Any] | None:
"""Fetch image bytes and build an image content block."""
try:
logger.debug("Fetching image from %s", image_url)
headers = None
if "uploads.linear.app" in image_url:
if linear_api_key is None:
linear_api_key = os.environ.get("LINEAR_API_KEY", "")
if linear_api_key:
headers = {"Authorization": linear_api_key}
else:
logger.warning(
"LINEAR_API_KEY not set; cannot authenticate image fetch for %s",
image_url,
)
response = await client.get(image_url, headers=headers)
response.raise_for_status()
content_type = response.headers.get("Content-Type", "").split(";")[0].strip()

View file

@ -15,7 +15,7 @@ from langgraph_sdk import get_client
# Local import for encryption
from .encryption import encrypt_token
from .utils.multimodal import extract_image_urls, fetch_image_block, strip_image_urls
from .utils.multimodal import extract_image_urls, fetch_image_block
logger = logging.getLogger(__name__)
@ -581,7 +581,6 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
description_image_urls = extract_image_urls(description)
if description_image_urls:
image_urls.extend(description_image_urls)
description = strip_image_urls(description, description_image_urls)
logger.debug(
"Found %d image URL(s) in issue description",
len(description_image_urls),
@ -643,7 +642,6 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
body_image_urls = extract_image_urls(body)
if body_image_urls:
image_urls.extend(body_image_urls)
body = strip_image_urls(body, body_image_urls)
logger.debug(
"Found %d image URL(s) in comment by %s",
len(body_image_urls),
@ -661,7 +659,6 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
trigger_image_urls = extract_image_urls(trigger_body)
if trigger_image_urls:
image_urls.extend(trigger_image_urls)
trigger_body = strip_image_urls(trigger_body, trigger_image_urls)
logger.debug(
"Found %d image URL(s) in triggering comment by %s",
len(trigger_image_urls),
@ -696,16 +693,9 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
async with httpx.AsyncClient() as client:
for image_url in image_urls:
headers = None
if "uploads.linear.app" in image_url:
if LINEAR_API_KEY:
headers = {"Authorization": LINEAR_API_KEY}
else:
logger.warning(
"LINEAR_API_KEY not set; cannot authenticate image fetch for %s",
image_url,
)
image_block = await fetch_image_block(image_url, client, headers=headers)
image_block = await fetch_image_block(
image_url, client, linear_api_key=LINEAR_API_KEY
)
if image_block:
content_blocks.append(image_block)
logger.info("Built %d content block(s) for prompt", len(content_blocks))