Skip to content

Commit 2fa15cc

Browse files
committed
fix: improve media failure diagnostics and token fallback coverage
1 parent fde530d commit 2fa15cc

3 files changed

Lines changed: 50 additions & 3 deletions

File tree

nanobot/channels/slack.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,9 +435,9 @@ async def _download_slack_file(self, file_info: dict[str, Any]) -> tuple[str | N
435435
marker = f"[{marker_type}: {name}]"
436436
url = str(file_info.get("url_private_download") or file_info.get("url_private") or "")
437437
if not url:
438-
return None, f"[{marker_type}: {name}: missing download url]"
438+
return None, self._download_failure_marker(marker_type, name, "missing download url")
439439
if not self.config.bot_token:
440-
return None, f"[{marker_type}: {name}: missing bot token]"
440+
return None, self._download_failure_marker(marker_type, name, "missing bot token")
441441

442442
filename = safe_filename(f"{file_id}_{name}")
443443
path = Path(get_media_dir("slack")) / filename
@@ -454,7 +454,14 @@ async def _download_slack_file(self, file_info: dict[str, Any]) -> tuple[str | N
454454
return str(path), marker
455455
except Exception as e:
456456
logger.warning("Failed to download Slack file {}: {}", file_id, e)
457-
return None, f"[{marker_type}: {name}: download failed]"
457+
return None, self._download_failure_marker(marker_type, name, "download failed")
458+
459+
@staticmethod
460+
def _download_failure_marker(marker_type: str, name: str, reason: str) -> str:
461+
return (
462+
f"[{marker_type}: {name}: {reason}; not available to nanobot. "
463+
"Check Slack files:read scope, reinstall the Slack app, and ensure the bot can access the file.]"
464+
)
458465

459466
@staticmethod
460467
def _looks_like_html_download(response: httpx.Response) -> bool:

tests/channels/test_slack_channel.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,14 @@ def test_slack_download_rejects_login_html() -> None:
643643
assert SlackChannel._looks_like_html_download(markdown_response) is False
644644

645645

646+
def test_slack_download_failure_marker_is_actionable() -> None:
647+
marker = SlackChannel._download_failure_marker("image", "screenshot.png", "download failed")
648+
649+
assert "not available to nanobot" in marker
650+
assert "files:read" in marker
651+
assert "reinstall the Slack app" in marker
652+
653+
646654
def test_slack_channel_uses_channel_aware_allow_policy() -> None:
647655
channel = SlackChannel(SlackConfig(enabled=True, allow_from=[]), MessageBus())
648656
assert channel.is_allowed("U1") is True
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from nanobot.utils.helpers import estimate_prompt_tokens_chain
2+
3+
4+
class _NoCounterProvider:
5+
pass
6+
7+
8+
class _BrokenCounterProvider:
9+
def estimate_prompt_tokens(self, messages, tools=None, model=None):
10+
raise RuntimeError("counter unavailable")
11+
12+
13+
def test_estimate_prompt_tokens_chain_falls_back_without_provider_counter() -> None:
14+
tokens, source = estimate_prompt_tokens_chain(
15+
_NoCounterProvider(),
16+
"test-model",
17+
[{"role": "user", "content": "hello"}],
18+
)
19+
20+
assert tokens > 0
21+
assert source == "tiktoken"
22+
23+
24+
def test_estimate_prompt_tokens_chain_falls_back_when_provider_counter_fails() -> None:
25+
tokens, source = estimate_prompt_tokens_chain(
26+
_BrokenCounterProvider(),
27+
"test-model",
28+
[{"role": "user", "content": "hello"}],
29+
)
30+
31+
assert tokens > 0
32+
assert source == "tiktoken"

0 commit comments

Comments
 (0)