feat(omni): Slack, WhatsApp, RCS, SMS and iMessage as Stream Chat messages - #738
Merged
Merged
Conversation
…es as Stream Chat messages Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
…ack message
- Channels are carried by providers behind a ProviderRegistry: Slack,
WhatsApp Cloud API, Google RBM (RCS), Twilio (SMS, plus its whatsapp:
and rcs: senders) and Linq (iMessage, RCS, SMS). Messages record their
provider in custom.omni.
- Stream ids are {provider}_{sha256 digest}: channels per account and
conversation, users per sender, always within Stream's limits.
- The Slack provider remembers recent messages, so one delivered as both
message and app_mention, or retried, is read once.
Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
…saging-a9e9 Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com>
tschellenbach
marked this pull request as ready for review
October 3, 2026 23:29
kanat
pushed a commit
that referenced
this pull request
Oct 5, 2026
…sages (#738) * feat(omni): standardize Slack, WhatsApp, RCS, SMS and iMessage messages as Stream Chat messages Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com> * feat(omni): providers per channel, hashed Stream ids, one read per Slack message - Channels are carried by providers behind a ProviderRegistry: Slack, WhatsApp Cloud API, Google RBM (RCS), Twilio (SMS, plus its whatsapp: and rcs: senders) and Linq (iMessage, RCS, SMS). Messages record their provider in custom.omni. - Stream ids are {provider}_{sha256 digest}: channels per account and conversation, users per sender, always within Stream's limits. - The Slack provider remembers recent messages, so one delivered as both message and app_mention, or retried, is read once. Co-authored-by: Thierry Schellenbach <thierryschellenbach@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Sprint 4: an agent should answer Slack, WhatsApp, RCS, SMS and iMessage from one Stream channel per conversation. This adds
vision-agents-plugins-omni(from vision_agents.plugins import omni).Each provider reads its webhook into an
OmniMessage, andto_streamturns that into a StreamMessageRequestwith attachments andcustom.omni. In the other direction,from_streamreads the agent's Stream reply back, and the provider'srenderbuilds the bodies for its send API.Changes
Providers per channel. A channel can be carried by more than one provider.
ProviderRegistryholds the providers by name and dispatchesparse(name, payload)andrender(message)onmessage.provider. It rejects a channel that provider doesn't carry.slackwhatsapp(Meta Cloud API)google_rbmtwiliowhatsapp:orrcs:prefixlinqserviceTo add a provider, subclass
OmniProviderwith a unique name and register it.Message model.
OmniMessage/OmniAttachmentwith kindsimage,video,audio,fileandlocation. Messages record theirprovider, which is also stored incustom.omni.Stream mapping.
image(image_url) orvideo/audio/file(asset_url) attachments, withmime_type,file_sizeandmedia_idon the attachment.locationattachment withlatitudeandlongitude.custom.omni.from_stream(reply, route=inbound)sends a reply back the way the inbound message came, and skipsai_reasoningandai_tool_callattachments.Stream ids.
stream_channel_idis{provider}_{first 24 hex chars of a sha256 of account and conversation}. That's one channel per conversation per business number or account, and always at most 64 characters.stream_user_idis{provider}_{digest of the sender}.Slack. The provider remembers recent
(channel, ts)pairs. A message delivered as bothmessageandapp_mention, or retried by Slack, is read once.Workspace. The package is registered in the workspace, with a
vision-agents[omni]extra. Itsuv.lockentries are written by hand:uv lockdoesn't resolve onacceleratebecause of an existing moondream/roboflow pillow conflict.UV_FROZEN=1 uv syncinstalls it.Only pure translation is included. There are no HTTP senders, no credentials, and no webhook signature checks.
UV_FROZEN=1 uv run --no-sync dev.py check: ruff, the extras validation, and mypy for the core package and plugins all pass. Unit tests: 1689 passed, including the 45 omni tests. 44 tests error, and they error the same way without this change: the Redis testcontainer fixture needs Docker, which this VM doesn't have.