feat(think): add multi-surface messenger ingress primitives#1776
feat(think): add multi-surface messenger ingress primitives#1776dcartertwo wants to merge 2 commits into
Conversation
Add generic Think messenger support for providers that do more than receive thread messages through a single HTTP webhook. Messengers can now define background ingress and route command or reaction events through the same durable Think reply pipeline. This lays groundwork for Discord Gateway, Slack slash commands/interactivity, Google Chat events, and other providers with mixed webhook, command, reaction, or background delivery surfaces. Also generalize messenger delivery from threads to Chat SDK postable surfaces, add command/reaction messenger context, document the Discord messenger design, and add a changeset for @cloudflare/think.
🦋 Changeset detectedLatest commit: 04da32d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| chat.onReaction(async (event) => { | ||
| const thread = event.thread as ChatThread; |
There was a problem hiding this comment.
🔴 Missing null guard for event.thread in onReaction handler causes crash
The onReaction handler at packages/think/src/messengers/chat-sdk.ts:445-446 casts event.thread directly to ChatThread without checking for null/undefined first. The onAction handler at packages/think/src/messengers/chat-sdk.ts:406-407 correctly guards with if (!event.thread) return; before the cast, but the onReaction handler omits this guard. If a ReactionEvent arrives from the Chat SDK without a thread (which is plausible given Chat SDK's ActionEvent can have a null thread), definitionForChatObject(thread) will call chatObject.toJSON() on a null value, throwing a TypeError at runtime.
| chat.onReaction(async (event) => { | |
| const thread = event.thread as ChatThread; | |
| chat.onReaction(async (event) => { | |
| if (!event.thread) return; | |
| const thread = event.thread as ChatThread; |
Was this helpful? React with 👍 or 👎 to provide feedback.
| private startBackgroundDefinition( | ||
| chat: Chat<Record<string, Adapter>>, | ||
| definition: NormalizedMessengerDefinition | ||
| ): Promise<void> { | ||
| const existing = this.backgroundStartTasks.get(definition.id); | ||
| if (existing) { | ||
| return existing; | ||
| } | ||
|
|
||
| const task = (async () => { | ||
| try { | ||
| await chat.initialize(); | ||
| await definition.background?.start({ | ||
| chat, | ||
| definition, | ||
| host: this.host, | ||
| messengerId: definition.id | ||
| }); | ||
| } catch (error) { | ||
| this.backgroundStartTasks.delete(definition.id); | ||
| console.error( | ||
| `Messenger ${definition.id} background ingress failed`, | ||
| error | ||
| ); | ||
| } | ||
| })(); | ||
|
|
||
| this.backgroundStartTasks.set(definition.id, task); | ||
| return task; |
There was a problem hiding this comment.
🚩 Background ingress task dedup relies on error-path cleanup only
In startBackgroundDefinition (packages/think/src/messengers/chat-sdk.ts:695-723), when the background start succeeds, the resolved promise remains in backgroundStartTasks permanently. Only the error path (catch at line 713-714) deletes the entry to allow retry. This means a successfully started background ingress can never be restarted through startBackgroundIngress — which appears intentional (start-once semantics). However, if the background ingress later fails or the Gateway shard disconnects, there's no mechanism at this layer to restart it; that responsibility falls entirely on the background ingress implementation itself (e.g., the shard DO's alarm-based reconnect). This is worth confirming matches the intended contract for MessengerBackgroundIngress.start().
Was this helpful? React with 👍 or 👎 to provide feedback.
agents
@cloudflare/ai-chat
@cloudflare/codemode
create-think
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
|
Addressed the AI review comments in
Re-verified with:
|
Summary
Adds provider-neutral Think messenger primitives for messengers that are not just thread-message webhooks.
This introduces background ingress, command events, reaction events, and channel-backed delivery surfaces so providers like Discord, Slack, Google Chat, and others can route mixed webhook/background/command/reaction surfaces through the same durable Think reply pipeline.
Also adds a Discord messenger RFC that captures the full planned Discord provider architecture.
What changed
MessengerBackgroundIngressandMessengerBackgroundContext.commandandreactionmessenger event kinds.MessengerCommandandMessengerReactioncontext types.onSlashCommand()andonReaction()into Think messenger routing.Threadto Chat SDK postable surfaces so command replies can target channels.@cloudflare/think.Why
Discord needs this for Gateway and Interactions, but the primitives are broader than Discord. Slack slash commands/interactivity, Google Chat events, and other messenger providers can use the same shape without adding provider-specific special cases to Think core.
Notes / Risk
Test plan
pnpm --filter @cloudflare/think run buildpnpm --filter @cloudflare/think exec vitest --run -c src/tests/vitest.config.ts src/tests/messengers.test.ts --reporter=dotpnpm --filter @cloudflare/think exec vitest --run -c src/tests/vitest.config.ts --reporter=dotpnpm run check