Skip to content

feat(teams): deliver outbound files via data-URI activity attachments#125

Open
patrick-chinchill wants to merge 3 commits into
mainfrom
feat/teams-file-attachments
Open

feat(teams): deliver outbound files via data-URI activity attachments#125
patrick-chinchill wants to merge 3 commits into
mainfrom
feat/teams-file-attachments

Conversation

@patrick-chinchill
Copy link
Copy Markdown
Collaborator

What

Ports upstream vercel/chat's filesToAttachments (packages/adapter-teams/src/index.ts lines ~1006-1035) to the Python Teams adapter. Until now the Teams adapter silently dropped a Postable's .files: post_message/edit_message never called extract_files, so execution artifacts (CSVs, charts, etc.) never reached Teams.

This is the Teams half of outbound artifact parity (the Slack half landed in #103/#117). It unblocks chinchill routing execution artifacts to Teams.

How

  • New TeamsAdapter._files_to_attachments(files) mirrors upstream filesToAttachments: for each FileUpload, resolve bytes via to_buffer(file.data, "teams", throw_on_unsupported=False), base64-encode via the existing buffer_to_data_uri, and build a Bot Framework attachment {"contentType": mime, "contentUrl": "data:<mime>;base64,<b64>", "name": filename}. mime defaults to application/octet-stream. Files whose data can't be resolved to bytes are skipped with a debug log (mirrors upstream's throwOnUnsupported: false + if (!buffer) continue).
  • post_message and edit_message now call extract_files(message) near the top and compute file_attachments. The adaptive-card branch appends the file attachments after the card attachment ([card, *file_attachments]); the text branch sets attachments to the file attachments when non-empty.

Caveat (inherited from upstream's design)

Data-URI attachments inline the full file payload base64-encoded inside the Bot Framework activity, so large files inflate the activity payload. This is the same size constraint upstream vercel/chat carries with its data-URI design; no chunking/upload-session path is added here (parity over redesign).

Deviations from upstream (deliberate)

  1. No fetch_data on Python FileUpload. The upstream TS FileUpload.data is Buffer | Blob | ArrayBuffer; the Python FileUpload (src/chat_sdk/types.py) carries only inline data: bytes (fetch_data lives on Attachment, not FileUpload). So the helper resolves data only — matching the sibling Slack _upload_files (file.data). The planned "file with fetch_data" test is swapped for a skip-unresolvable-bytes test, which exercises the genuine Python analog of upstream's if (!buffer) continue.
  2. edit_message file delivery is a deliberate, task-directed superset of this upstream version. In the pinned upstream checkout, filesToAttachments is wired into postMessage and postChannelMessagenot editMessage. The Python adapter has no post_channel_message (so that upstream site has no port). edit_message file delivery is added here per outbound-artifact-parity intent and is covered by a dedicated test; it reads as an intentional superset, not an accidental over-port.

Tests

TestFileAttachments in tests/test_teams_adapter.py (5 tests): text+file, adaptive-card+file (both attachments present), edit_message+file, mime-type default to application/octet-stream, and the skip-unresolvable-bytes branch. Reverting the extract_files wiring makes the four delivery tests fail (verified).

Checks

  • Full suite green: 4052 passed, 3 skipped (was ~4046; +5 new tests, +1).
  • ruff check / ruff format --check clean; pyrefly check 0 errors; audit_test_quality.py 0 hard failures.
  • Version bumped 0.4.29a2 -> 0.4.29a3; CHANGELOG entry added.

🤖 Generated with Claude Code

Port upstream filesToAttachments (packages/adapter-teams/src/index.ts
~1006-1035) to Python. The Teams adapter previously dropped a Postable's
.files entirely -- post_message/edit_message never called extract_files,
so execution artifacts silently vanished.

Adds TeamsAdapter._files_to_attachments: resolves each FileUpload's bytes
via to_buffer(..., throw_on_unsupported=False), base64-encodes them, and
builds a Bot Framework attachment {contentType, contentUrl:
data:<mime>;base64,<b64>, name}. mime defaults to
application/octet-stream; files whose data can't be resolved to bytes are
skipped with a debug log (mirrors upstream's `if (!buffer) continue`).

post_message and edit_message now call extract_files and attach the
results: the adaptive-card branch appends file attachments after the card
attachment; the text branch sets attachments to the file attachments when
present.

Adds TestFileAttachments (5 tests): text+file, card+file (both
attachments present), edit_message+file, octet-stream default, and the
skip-unresolvable-bytes branch.

Bumps version to 0.4.29a3 and adds a CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Warning

Review limit reached

@patrick-chinchill, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 29 minutes and 50 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fa39b8c9-eab0-4688-8acb-8b7bf41ba8cd

📥 Commits

Reviewing files that changed from the base of the PR and between 3ba6456 and d670b50.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • pyproject.toml
  • src/chat_sdk/adapters/teams/adapter.py
  • tests/test_teams_adapter.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements outbound file delivery for the Teams adapter by converting FileUpload objects into base64 data-URI activity attachments. It introduces the _files_to_attachments helper method, integrates it into post_message and edit_message, and adds comprehensive unit tests to cover various attachment scenarios and edge cases. As there are no review comments, I have no additional feedback to provide.

The initial port wired filesToAttachments into both post_message AND
edit_message. Upstream vercel/chat wires it into postMessage +
postChannelMessage only — editMessage never carries files. And chinchill
delivers execution artifacts via a fresh post(), never by editing files
into an existing message. Carrying files in edit_message was an
unrequested divergence (and an edit_message+files test has no upstream
counterpart, which the repo's verify_test_fidelity gate would flag).

Revert edit_message to file-free, mirroring upstream. Replace the
edit_message+file test with a fidelity guard asserting edit_message
carries no file attachments. post_message file delivery (the actual
parity fix) is unchanged.

Full suite: 4052 passed. pyrefly clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@patrick-chinchill
Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

… caveats

Self-review (two adversarial reviewers) found:
- MEDIUM: multi-file handling untested — mutation showed return attachments[:1]
  (drop all but first) and reversed() both passed green, so a regression that
  drops/reorders artifacts would merge undetected, defeating the parity goal.
  Added test_multiple_files_attached_in_order (N files -> N attachments, input
  order) + test_partial_skip_preserves_surviving_files (good/bad/good -> survivors
  in order). Mutation-verified: [:1] now FAILS the multi-file test.
- LOW: the skip test's  was hollow (post_message
  emits an unconditional 'send (message)' debug, so it passed even if the skip
  branch logged nothing). Now asserts the SPECIFIC 'unsupported data' skip log.
- Tightened CHANGELOG caveats: Bot Framework documents data-URI contentUrl as
  IMAGE-ONLY — image/* (charts) render inline, but text/csv/application/pdf may
  NOT surface on Teams (which uses a file-consent/hosted-URL flow for arbitrary
  files). So this delivers reliable Teams parity for images; non-image delivery
  may need the file-consent path as a follow-up. Also noted the ~256KB data-URI
  ceiling. Verify in a real Teams tenant before relying on either.

Full suite: 4054 passed. ruff + pyrefly clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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