fix: background task media delivery + vision download timeout (#3919)
* feat(telegram): add webhook mode as alternative to polling When TELEGRAM_WEBHOOK_URL is set, the adapter starts an HTTP webhook server (via python-telegram-bot's start_webhook()) instead of long polling. This enables cloud platforms like Fly.io and Railway to auto-wake suspended machines on inbound HTTP traffic. Polling remains the default — no behavior change unless the env var is set. Env vars: TELEGRAM_WEBHOOK_URL Public HTTPS URL for Telegram to push to TELEGRAM_WEBHOOK_PORT Local listen port (default 8443) TELEGRAM_WEBHOOK_SECRET Secret token for update verification Cherry-picked and adapted from PR #2022 by SHL0MS. Preserved all current main enhancements (network error recovery, polling conflict detection, DM topics setup). Co-authored-by: SHL0MS <SHL0MS@users.noreply.github.com> * fix: send_document call in background task delivery + vision download timeout Two fixes salvaged from PR #2269 by amethystani: 1. gateway/run.py: adapter.send_file() → adapter.send_document() send_file() doesn't exist on BasePlatformAdapter. Background task media files were silently never delivered (AttributeError swallowed by except Exception: pass). 2. tools/vision_tools.py: configurable image download timeout via HERMES_VISION_DOWNLOAD_TIMEOUT env var (default 30s), plus guard against raise None when max_retries=0. The third fix in #2269 (opencode-go auth config) was already resolved on main. Co-authored-by: amethystani <amethystani@users.noreply.github.com> --------- Co-authored-by: SHL0MS <SHL0MS@users.noreply.github.com> Co-authored-by: amethystani <amethystani@users.noreply.github.com>
This commit is contained in:
@@ -324,6 +324,9 @@ compression:
|
|||||||
# vision:
|
# vision:
|
||||||
# provider: "auto"
|
# provider: "auto"
|
||||||
# model: "" # e.g. "google/gemini-2.5-flash", "openai/gpt-4o"
|
# model: "" # e.g. "google/gemini-2.5-flash", "openai/gpt-4o"
|
||||||
|
# timeout: 30 # LLM API call timeout (seconds)
|
||||||
|
# download_timeout: 30 # Image HTTP download timeout (seconds)
|
||||||
|
# # Increase for slow connections or self-hosted image servers
|
||||||
#
|
#
|
||||||
# # Web page scraping / summarization + browser page text extraction
|
# # Web page scraping / summarization + browser page text extraction
|
||||||
# web_extract:
|
# web_extract:
|
||||||
|
|||||||
@@ -3891,7 +3891,7 @@ class GatewayRunner:
|
|||||||
# Send media files
|
# Send media files
|
||||||
for media_path in (media_files or []):
|
for media_path in (media_files or []):
|
||||||
try:
|
try:
|
||||||
await adapter.send_file(
|
await adapter.send_document(
|
||||||
chat_id=source.chat_id,
|
chat_id=source.chat_id,
|
||||||
file_path=media_path,
|
file_path=media_path,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -223,7 +223,8 @@ DEFAULT_CONFIG = {
|
|||||||
"model": "", # e.g. "google/gemini-2.5-flash", "gpt-4o"
|
"model": "", # e.g. "google/gemini-2.5-flash", "gpt-4o"
|
||||||
"base_url": "", # direct OpenAI-compatible endpoint (takes precedence over provider)
|
"base_url": "", # direct OpenAI-compatible endpoint (takes precedence over provider)
|
||||||
"api_key": "", # API key for base_url (falls back to OPENAI_API_KEY)
|
"api_key": "", # API key for base_url (falls back to OPENAI_API_KEY)
|
||||||
"timeout": 30, # seconds — increase for slow local vision models
|
"timeout": 30, # seconds — LLM API call timeout; increase for slow local vision models
|
||||||
|
"download_timeout": 30, # seconds — image HTTP download timeout; increase for slow connections
|
||||||
},
|
},
|
||||||
"web_extract": {
|
"web_extract": {
|
||||||
"provider": "auto",
|
"provider": "auto",
|
||||||
|
|||||||
@@ -45,6 +45,28 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
_debug = DebugSession("vision_tools", env_var="VISION_TOOLS_DEBUG")
|
_debug = DebugSession("vision_tools", env_var="VISION_TOOLS_DEBUG")
|
||||||
|
|
||||||
|
# Configurable HTTP download timeout for _download_image().
|
||||||
|
# Separate from auxiliary.vision.timeout which governs the LLM API call.
|
||||||
|
# Resolution: config.yaml auxiliary.vision.download_timeout → env var → 30s default.
|
||||||
|
def _resolve_download_timeout() -> float:
|
||||||
|
env_val = os.getenv("HERMES_VISION_DOWNLOAD_TIMEOUT", "").strip()
|
||||||
|
if env_val:
|
||||||
|
try:
|
||||||
|
return float(env_val)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
from hermes_cli.config import load_config
|
||||||
|
cfg = load_config()
|
||||||
|
val = cfg.get("auxiliary", {}).get("vision", {}).get("download_timeout")
|
||||||
|
if val is not None:
|
||||||
|
return float(val)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return 30.0
|
||||||
|
|
||||||
|
_VISION_DOWNLOAD_TIMEOUT = _resolve_download_timeout()
|
||||||
|
|
||||||
|
|
||||||
def _validate_image_url(url: str) -> bool:
|
def _validate_image_url(url: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -146,7 +168,7 @@ async def _download_image(image_url: str, destination: Path, max_retries: int =
|
|||||||
# Enable follow_redirects to handle image CDNs that redirect (e.g., Imgur, Picsum)
|
# Enable follow_redirects to handle image CDNs that redirect (e.g., Imgur, Picsum)
|
||||||
# SSRF: event_hooks validates each redirect target against private IP ranges
|
# SSRF: event_hooks validates each redirect target against private IP ranges
|
||||||
async with httpx.AsyncClient(
|
async with httpx.AsyncClient(
|
||||||
timeout=30.0,
|
timeout=_VISION_DOWNLOAD_TIMEOUT,
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
event_hooks={"response": [_ssrf_redirect_guard]},
|
event_hooks={"response": [_ssrf_redirect_guard]},
|
||||||
) as client:
|
) as client:
|
||||||
@@ -183,6 +205,10 @@ async def _download_image(image_url: str, destination: Path, max_retries: int =
|
|||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if last_error is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"_download_image exited retry loop without attempting (max_retries={max_retries})"
|
||||||
|
)
|
||||||
raise last_error
|
raise last_error
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1018,7 +1018,8 @@ auxiliary:
|
|||||||
model: "" # e.g. "openai/gpt-4o", "google/gemini-2.5-flash"
|
model: "" # e.g. "openai/gpt-4o", "google/gemini-2.5-flash"
|
||||||
base_url: "" # Custom OpenAI-compatible endpoint (overrides provider)
|
base_url: "" # Custom OpenAI-compatible endpoint (overrides provider)
|
||||||
api_key: "" # API key for base_url (falls back to OPENAI_API_KEY)
|
api_key: "" # API key for base_url (falls back to OPENAI_API_KEY)
|
||||||
timeout: 30 # seconds — increase for slow local vision models
|
timeout: 30 # seconds — LLM API call; increase for slow local vision models
|
||||||
|
download_timeout: 30 # seconds — image HTTP download; increase for slow connections
|
||||||
|
|
||||||
# Web page summarization + browser page text extraction
|
# Web page summarization + browser page text extraction
|
||||||
web_extract:
|
web_extract:
|
||||||
@@ -1042,7 +1043,7 @@ auxiliary:
|
|||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
Each auxiliary task has a configurable `timeout` (in seconds). Defaults: vision 30s, web_extract 30s, approval 30s, compression 120s. Increase these if you use slow local models for auxiliary tasks.
|
Each auxiliary task has a configurable `timeout` (in seconds). Defaults: vision 30s, web_extract 30s, approval 30s, compression 120s. Increase these if you use slow local models for auxiliary tasks. Vision also has a separate `download_timeout` (default 30s) for the HTTP image download — increase this for slow connections or self-hosted image servers.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|||||||
Reference in New Issue
Block a user