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
12 changes: 11 additions & 1 deletion gateway/platforms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2126,8 +2126,18 @@ def extract_media(content: str) -> Tuple[List[Tuple[str, bool]], str]:

# Extract MEDIA:<path> tags, allowing optional whitespace after the colon
# and quoted/backticked paths for LLM-formatted outputs.
media_exts = (
r"png|jpe?g|gif|webp|mp4|mov|avi|mkv|webm|ogg|opus|mp3|wav|"
r"m4a|flac|epub|pdf|zip|rar|7z|docx?|xlsx?|pptx?|txt|csv|apk|ipa"
)
path_with_known_ext = (
r"(?:~/|/|[A-Za-z]:[\\/]|\\\\)"
r"\S+(?:[^\S\n]+\S+)*?"
r"\.(?:%s)(?=[\s`\"',;:)\]}]|$)" % media_exts
)
media_pattern = re.compile(
r'''[`"']?MEDIA:\s*(?P<path>`[^`\n]+`|"[^"\n]+"|'[^'\n]+'|(?:~/|/)\S+(?:[^\S\n]+\S+)*?\.(?:png|jpe?g|gif|webp|mp4|mov|avi|mkv|webm|ogg|opus|mp3|wav|m4a|flac|epub|pdf|zip|rar|7z|docx?|xlsx?|pptx?|txt|csv|apk|ipa)(?=[\s`"',;:)\]}]|$)|\S+)[`"']?'''
r'''[`"']?MEDIA:\s*(?P<path>`[^`\n]+`|"[^"\n]+"|'[^'\n]+'|%s|\S+)[`"']?'''
% path_with_known_ext
)
for match in media_pattern.finditer(content):
path = match.group("path").strip()
Expand Down
5 changes: 5 additions & 0 deletions gateway/platforms/discord.py
Original file line number Diff line number Diff line change
Expand Up @@ -3658,6 +3658,11 @@ async def _fetch_channel_context(
if limit <= 0:
return ""

# Not all channel-like objects expose ``history``. Forum parents,
# voice channels, and custom proxies in tests can lack it.
if not hasattr(channel, "history"):
return ""

# Determine which bot messages to include in context
allow_bots_raw = os.getenv("DISCORD_ALLOW_BOTS", "none").lower().strip()
include_other_bots = allow_bots_raw != "none"
Expand Down
16 changes: 16 additions & 0 deletions tests/gateway/test_discord_free_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,22 @@ def history(self, *, limit, before, after=None, oldest_first=None):
assert recorded_after["value"] is None


@pytest.mark.asyncio
async def test_fetch_channel_context_returns_empty_when_channel_lacks_history(adapter, monkeypatch):
"""Channel-like objects without ``.history`` should not break backfill."""
monkeypatch.setenv("DISCORD_ALLOW_BOTS", "all")
adapter.config.extra["history_backfill_limit"] = 10

channel = SimpleNamespace(id=123, name="general")

result = await adapter._fetch_channel_context(
channel,
before=SimpleNamespace(id=42),
)

assert result == ""


@pytest.mark.asyncio
async def test_discord_shared_channel_backfill_prepends_context(adapter, monkeypatch):
monkeypatch.setenv("DISCORD_REQUIRE_MENTION", "true")
Expand Down
9 changes: 9 additions & 0 deletions tests/gateway/test_platform_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,15 @@ def test_media_tag_supports_unquoted_flac_paths_with_spaces(self):
assert media == [("/tmp/Jane Doe/speech.flac", False)]
assert cleaned == ""

def test_media_tag_supports_unquoted_windows_paths_with_unicode_spaces(self):
path = "C:\\Users\\\u0418\u0432\u0430\u043d \u0418\u0432\u0430\u043d\u043e\u0432\\voice file.ogg"
content = f"Here is the voice:\nMEDIA:{path}\nDone"
media, cleaned = BasePlatformAdapter.extract_media(content)
assert media == [(path, False)]
assert "MEDIA:" not in cleaned
assert "Here is the voice:" in cleaned
assert "Done" in cleaned

def test_as_document_directive_stripped_from_cleaned_text(self):
"""[[as_document]] is a routing directive — strip it from
user-visible text just like [[audio_as_voice]]. Callers detect the
Expand Down
22 changes: 18 additions & 4 deletions tests/run_agent/test_provider_parity.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,12 @@ def test_original_messages_not_mutated(self, monkeypatch):
assert messages[0]["role"] == "system"

def test_developer_role_via_nous_portal(self, monkeypatch):
agent = _make_agent(monkeypatch, "nous", base_url="https://inference-api.nousresearch.com/v1")
agent.model = "gpt-5"
agent = _make_agent(
monkeypatch,
"nous",
base_url="https://inference-api.nousresearch.com/v1",
model="gpt-5",
)
messages = [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "hi"},
Expand Down Expand Up @@ -344,14 +348,24 @@ def test_includes_tools(self, monkeypatch):
class TestBuildApiKwargsNousPortal:
def test_includes_nous_product_tags(self, monkeypatch):
from agent.portal_tags import nous_portal_tags
agent = _make_agent(monkeypatch, "nous", base_url="https://inference-api.nousresearch.com/v1")
agent = _make_agent(
monkeypatch,
"nous",
base_url="https://inference-api.nousresearch.com/v1",
model="gpt-5",
)
messages = [{"role": "user", "content": "hi"}]
kwargs = agent._build_api_kwargs(messages)
extra = kwargs.get("extra_body", {})
assert extra.get("tags") == nous_portal_tags()

def test_uses_chat_completions_format(self, monkeypatch):
agent = _make_agent(monkeypatch, "nous", base_url="https://inference-api.nousresearch.com/v1")
agent = _make_agent(
monkeypatch,
"nous",
base_url="https://inference-api.nousresearch.com/v1",
model="gpt-5",
)
messages = [{"role": "user", "content": "hi"}]
kwargs = agent._build_api_kwargs(messages)
assert "messages" in kwargs
Expand Down
Loading