fix(adapters): Slack streaming team_id + Teams DM Graph conversation IDs (vercel/chat#330, #403)#85
Conversation
vercel/chat#330) Slack carries the workspace ID in different shapes depending on the webhook envelope. Block_actions / view_submission payloads use a nested ``team.id`` (object) with ``user.team_id`` as a fallback, while message events use the top-level ``team_id`` / ``team`` (string). The previous ``raw.get("team_id") or raw.get("team")`` extraction returned the entire ``team`` dict for block_actions, causing Slack streaming API calls to fail or hit the wrong workspace. Move the extraction into a dedicated helper that walks each shape in order and returns ``None`` when no string ID is found. https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj
…est (vercel/chat#330) Upstream's chat@4.27.0 added a thread.test.ts case verifying that a block_actions-created thread can stream structured chunks (text + TaskUpdateChunk) through ``adapter.stream`` with the resolved ``recipient_team_id``. Port it to keep test_fidelity at 0 missing for the thread.test.ts mapping. https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj
…l/chat#403) Bot Framework hands out opaque DM conversation IDs (e.g. ``a:1xWhatever``) which Microsoft Graph's ``/chats/{chat-id}/messages`` endpoint rejects with 404. The canonical Graph chat ID for a 1:1 DM is ``19:{userAadId}_{botId}@unq.gbl.spaces``. Cache the user's AAD object ID from incoming activities and resolve the Graph chat ID before issuing Graph calls. Add a discriminated union ``TeamsGraphContext`` (channel | DM) and dispatch on context type from ``fetch_messages``, ``fetch_channel_messages``, and ``fetch_channel_info``. Group chats (no cached context) keep falling back to the raw conversation ID, which works as-is with Graph. Backwards-compatible with the pre-#403 cache shape: entries without a ``type`` discriminator are treated as ``channel``. https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request implements canonical Microsoft Graph chat ID resolution for Teams DMs to prevent 404 errors and improves Slack workspace ID extraction from interactive payloads. The Teams adapter now caches and utilizes a broader Graph context for both channels and direct messages, ensuring the correct IDs are used for Graph API calls. Additionally, a new helper function in thread.py ensures correct Slack team ID parsing across different webhook shapes. Feedback was provided regarding duplicated pagination logic in the Teams adapter, suggesting a refactor to improve maintainability.
| chat_id = self._chat_id_from_context(graph_context, base_conversation_id) | ||
| if direction == "forward": | ||
| params = {"$top": limit, "$orderby": "createdDateTime asc"} | ||
| if options.cursor: | ||
| params["$filter"] = f"createdDateTime gt {options.cursor}" | ||
| graph_messages = await self._graph_list_chat_messages(chat_id, params) | ||
| has_more = len(graph_messages) >= limit | ||
| else: | ||
| params = {"$top": limit, "$orderby": "createdDateTime desc"} | ||
| if options.cursor: | ||
| params["$filter"] = f"createdDateTime lt {options.cursor}" | ||
| graph_messages = await self._graph_list_chat_messages(chat_id, params) | ||
| graph_messages.reverse() | ||
| has_more = len(graph_messages) >= limit |
There was a problem hiding this comment.
The logic for handling forward and backward pagination is duplicated here and in fetch_messages (lines 1092-1110). To improve maintainability and reduce code duplication, you could refactor this block and apply a similar change in fetch_messages.
Here's a suggested refactoring for this block:
| chat_id = self._chat_id_from_context(graph_context, base_conversation_id) | |
| if direction == "forward": | |
| params = {"$top": limit, "$orderby": "createdDateTime asc"} | |
| if options.cursor: | |
| params["$filter"] = f"createdDateTime gt {options.cursor}" | |
| graph_messages = await self._graph_list_chat_messages(chat_id, params) | |
| has_more = len(graph_messages) >= limit | |
| else: | |
| params = {"$top": limit, "$orderby": "createdDateTime desc"} | |
| if options.cursor: | |
| params["$filter"] = f"createdDateTime lt {options.cursor}" | |
| graph_messages = await self._graph_list_chat_messages(chat_id, params) | |
| graph_messages.reverse() | |
| has_more = len(graph_messages) >= limit | |
| chat_id = self._chat_id_from_context(graph_context, base_conversation_id) | |
| order_by = "createdDateTime asc" | |
| filter_op = "gt" | |
| if direction != "forward": | |
| order_by = "createdDateTime desc" | |
| filter_op = "lt" | |
| params = {"$top": limit, "$orderby": order_by} | |
| if options.cursor: | |
| params["$filter"] = f"createdDateTime {filter_op} {options.cursor}" | |
| graph_messages = await self._graph_list_chat_messages(chat_id, params) | |
| has_more = len(graph_messages) >= limit | |
| if direction != "forward": | |
| graph_messages.reverse() |
Summary
Two small upstream bug-fix ports bundled into one PR. Both touch adapter dispatch code and have new regression tests that fail before the fix.
fix(slack): interactive-payload team_id through streaming context — vercel/chat#330
Slack carries the workspace ID in different shapes depending on the webhook envelope:
message,app_mention): top-levelteam_id/team(string).block_actions,view_submission, …): nestedteam.id(object), withuser.team_idas a final fallback.The old extraction (
raw.get("team_id") or raw.get("team")) returned the entireteamdict forblock_actions, which then traveled to the Slack adapter asrecipient_team_idand either crashed Slack streaming API calls or routed them to the wrong workspace.Moved the extraction into a dedicated
_extract_slack_recipient_team_idhelper insrc/chat_sdk/thread.pythat walks each shape in order and returnsNonewhen no string ID is found.fix(teams): canonical DM conversation ID for Microsoft Graph API — vercel/chat#403
Bot Framework hands out opaque DM conversation IDs (e.g.
a:1xWhatever) which Graph's/chats/{chat-id}/messagesendpoint rejects with 404. The canonical Graph chat ID for a 1:1 DM is19:{userAadId}_{botId}@unq.gbl.spaces.from.aadObjectId) into a newTeamsDmContextkeyed by base conversation ID.TeamsGraphContext = TeamsChannelContext | TeamsDmContext._get_channel_context→_get_graph_contextand add_chat_id_from_context()dispatch helper.fetch_messages,fetch_channel_messages, andfetch_channel_infoto dispatch on context type.typediscriminator and are treated aschannel.Tests
tests/test_thread_faithful.py:test_should_pass_stream_options_from_current_message_contextover all four Slack payload shapes (team_id,teamstring,team.idobject,user.team_idfallback).test_concurrent_block_actions_team_ids_do_not_cross_contaminatecovering hazard chore: bump to 0.0.1a3 #6 (no team_id leak across concurrent requests).test_should_forward_structured_stream_chunks_to_adapter_stream_from_an_action_created_thread(port of upstream's #330 test; restores 0-missing onthread.test.tsfidelity).tests/test_teams_coverage.py: newTestGraphDmConversationIdResolutionclass with 8 cases covering_chat_id_from_context(DM / channel / no-context branches),_cache_user_context(DM cached / channel skipped / no-aad skipped / DM-like channel adversarial), and end-to-endfetch_messages(DM resolves to19:{aadId}_{botId}@unq.gbl.spaces, group chat falls back to raw ID).Adversarial checks ran per
docs/SELF_REVIEW.md: pass-interaction (concurrent team_ids) and the "DM-like channel" misclassification both have explicit tests.Test plan
uv run ruff check src/ tests/ scripts/— cleanuv run ruff format --check src/ tests/ scripts/— cleanuv run python scripts/audit_test_quality.py— 0 hard failures (39 pre-existing warnings unchanged)TS_ROOT=/tmp/vercel-chat uv run python scripts/verify_test_fidelity.py—thread.test.tsnow reports 0 missinguv run pytest tests/ --tb=short -q—3681 passed, 2 skipped, 1 failedtests/test_github_webhook.py::TestGitHubAdapterConstructor::test_throws_when_no_auth(called out as ignorable in the task brief)Upstream refs
[chat] fix Slack streaming team ID for interactive payloads(commit8a0c7b3)fix(adapter-teams): resolve DM conversation IDs for Graph API(commit4c24c94)https://claude.ai/code/session_01FyMxQn2BEAzmwKS1GZczKj
Generated by Claude Code