Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gateway/platforms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,7 @@ class MessageEvent:
# Reply context
reply_to_message_id: Optional[str] = None
reply_to_text: Optional[str] = None # Text of the replied-to message (for context injection)
reply_to_audio_path: Optional[str] = None # Local path to cached audio of replied-to voice message

# Auto-loaded skill(s) for topic/channel bindings (e.g., Telegram DM Topics,
# Discord channel_skill_bindings). A single name or ordered list.
Expand Down
28 changes: 28 additions & 0 deletions gateway/platforms/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -2887,6 +2887,7 @@ async def _handle_text_message(self, update: Update, context: ContextTypes.DEFAU

event = self._build_message_event(update.message, MessageType.TEXT, update_id=update.update_id)
event.text = self._clean_bot_trigger_text(event.text)
await self._enrich_event_with_replied_audio(event, update.message)
self._enqueue_text_event(event)

async def _handle_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
Expand Down Expand Up @@ -3089,6 +3090,9 @@ async def _handle_media_message(self, update: Update, context: ContextTypes.DEFA
if msg.caption:
event.text = self._clean_bot_trigger_text(msg.caption)

# Enrich with replied-to voice/audio if applicable
await self._enrich_event_with_replied_audio(event, msg)

# Handle stickers: describe via vision tool with caching
if msg.sticker:
await self._handle_sticker(msg, event)
Expand Down Expand Up @@ -3524,6 +3528,7 @@ def _build_message_event(
# Extract reply context if this message is a reply
reply_to_id = None
reply_to_text = None
reply_to_audio_path = None
if message.reply_to_message:
reply_to_id = str(message.reply_to_message.message_id)
reply_to_text = message.reply_to_message.text or message.reply_to_message.caption or None
Expand Down Expand Up @@ -3551,6 +3556,29 @@ def _build_message_event(
timestamp=message.date,
)

async def _enrich_event_with_replied_audio(self, event: "MessageEvent", message: Message) -> None:
"""If the triggering message is a reply to a voice/audio note, download and cache
that audio so the STT pipeline can transcribe it as context."""
if not message.reply_to_message:
return
replied = message.reply_to_message
if replied.voice:
try:
file_obj = await replied.voice.get_file()
audio_bytes = await file_obj.download_as_bytearray()
event.reply_to_audio_path = cache_audio_from_bytes(bytes(audio_bytes), ext=".ogg")
logger.info("[Telegram] Cached replied-to voice at %s", event.reply_to_audio_path)
except Exception as e:
logger.warning("[Telegram] Failed to cache replied-to voice: %s", e, exc_info=True)
elif replied.audio:
try:
file_obj = await replied.audio.get_file()
audio_bytes = await file_obj.download_as_bytearray()
event.reply_to_audio_path = cache_audio_from_bytes(bytes(audio_bytes), ext=".mp3")
logger.info("[Telegram] Cached replied-to audio at %s", event.reply_to_audio_path)
except Exception as e:
logger.warning("[Telegram] Failed to cache replied-to audio: %s", e, exc_info=True)

# ── Message reactions (processing lifecycle) ──────────────────────────

def _reactions_enabled(self) -> bool:
Expand Down
10 changes: 10 additions & 0 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5542,6 +5542,16 @@ async def _prepare_inbound_message_text(
# guess (or answer for both subjects). Token overhead is minimal.
reply_snippet = event.reply_to_text[:500]
message_text = f'[Replying to: "{reply_snippet}"]\n\n{message_text}'
elif getattr(event, "reply_to_audio_path", None) and event.reply_to_message_id:
# Replied-to message was a voice/audio note — transcribe it and inject as context
try:
transcribed = await self._enrich_message_with_transcription("", [event.reply_to_audio_path])
transcribed = transcribed.strip()
if transcribed and not any(m in transcribed for m in ("No STT provider", "STT is disabled", "can't listen")):
message_text = f'[Replying to voice message: "{transcribed[:500]}"]\n\n{message_text}'
logger.info("[Gateway] Injected replied-to voice transcription as context")
except Exception as _e:
logger.warning("[Gateway] Failed to transcribe replied-to audio: %s", _e)

if "@" in message_text:
try:
Expand Down
3 changes: 3 additions & 0 deletions hermes_cli/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ def _xai_curated_models() -> list[str]:
"copilot-acp",
],
"copilot": [
"gpt-5.5",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5-mini",
Expand All @@ -211,6 +212,8 @@ def _xai_curated_models() -> list[str]:
"gpt-4.1",
"gpt-4o",
"gpt-4o-mini",
"claude-opus-4.7",
"claude-opus-4.6",
"claude-sonnet-4.6",
"claude-sonnet-4",
"claude-sonnet-4.5",
Expand Down