Skip to content

feat: room file tools behind Capability.FILES - #525

Draft
eric-descourtis-thenvoi wants to merge 1 commit into
band-ai:mainfrom
eric-descourtis-thenvoi:feat/file-transfer-tools
Draft

feat: room file tools behind Capability.FILES#525
eric-descourtis-thenvoi wants to merge 1 commit into
band-ai:mainfrom
eric-descourtis-thenvoi:feat/file-transfer-tools

Conversation

@eric-descourtis-thenvoi

Copy link
Copy Markdown

What

Three built-in room file tools behind a new opt-in Capability.FILES:

Tool Does
band_list_room_files Lists files shared with this agent, newest first (name/type/size + sender)
band_read_room_file Text inline (16 KiB cap); images become real vision input; other formats described
band_send_room_file Uploads a text file it wrote and attaches it to a mention-reply

Plus two Claude SDK bridge fixes the vision path needs:

  • _make_result passes through results that are already MCP-shaped ({"content": [...]}) instead of json-dumping an image block into base64 prose.
  • A custom tool that declares room_id as a real input field keeps it; the strip now applies only to the schema-injected extra.

How

  • The generated REST client (band-client-rest==0.0.10) doesn't expose the agent file endpoints yet, so the tools use the client's own transport (same base URL/auth/pool) with a comment marking the migration point once Fern regen lands.
  • Tool-family registries treat files exactly like memory: excluded from BASE/CHAT, present in ALL, own FILE_TOOL_NAMES set and "files" category. band_send_room_file counts as a room-posting tool for reply suppression; the two readers join READ_ONLY_TOOL_NAMES.
  • Wired adapters: claude_sdk (definition-driven, vision capable), pydantic_ai and crewai/crewai_flow (concrete wrappers). Other adapters keep their current capability set — enabling them is a follow-up per adapter.
  • Mentions resolve client-side via the existing participants cache, so the send works against platforms without server-side handle resolution.
  • Listing queries the delivery-status views (processing/processed/pending/unfiltered) because the message that triggered the current turn is still processing — skip that view and "the file you were just sent" doesn't exist yet.

Platform dependency

The endpoints ship in thenvoi-platform PLT-1209/1210/1211 (in review). The capability defaults off everywhere, so this is safe to merge ahead of them; agents opt in with AdapterFeatures(capabilities={Capability.FILES}).

Proven end-to-end on a dev platform node: an agent read a text file word-for-word, described a webp book cover from pixels, and authored+attached its own file — the same flows now covered by 18 unit tests (tests/runtime/test_file_tools.py, tests/integrations/claude_sdk/test_mcp_content_passthrough.py).

Tests

  • uv run pytest tests/ --ignore=tests/integration/ --ignore=tests/e2e/: 4559 passed; the only residue is the pre-existing desktop_app full-run flake, identical on clean main (passes in isolation both there and here).
  • ruff check / ruff format / pyrefly check: clean.

🤖 Generated with Claude Code

Adds three built-in agent tools for the platform's file-transfer surface:
band_list_room_files, band_read_room_file, band_send_room_file. They speak
to the agent file endpoints through the REST client's own transport (the
generated client does not expose them yet) and are gated behind the new
Capability.FILES, default off, because the endpoints require a deployment
with file storage configured.

read_room_file returns images as MCP-shaped content so runtimes that
forward MCP blocks give the model real vision input; the Claude SDK bridge
now passes such results through instead of json-dumping them into a text
block, and stops stripping a custom tool's room_id argument when the input
model declares that field as part of its own contract.

CrewAI and PydanticAI get concrete wrappers; the tool-family registries,
prompts drift checks, protocol, and testing fake are extended accordingly.
Tool-name sets treat files like memory: excluded from BASE/CHAT, included
in ALL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@eric-descourtis-thenvoi

Copy link
Copy Markdown
Author

Dogfooding note: while live-testing this branch, agent turns went silent-empty (~1s, $0.00, no tool calls) and the adapter logged nothing. A probe with ClaudeAgentOptions(stderr=...) showed the real cause: RateLimitEvent (five-hour subscription limit) with a synthetic "session limit" assistant message and is_error=True on the result. Two adapter gaps worth follow-ups, not in this PR's scope:

  1. ResultMessage.is_error / RateLimitEvent should be logged (or surfaced as a band error event) instead of reading as a successful $0.0000 turn.
  2. Resuming a dead claude_sdk_session_id doesn't raise — the CLI accepts the id and returns empty turns forever, so the adapter's resume-failure fallback never fires. The ACP adapter's validate-then-fallback pattern (session/load) is the model to copy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant