Skip to content

feat(think): add multi-surface messenger ingress primitives#1776

Open
dcartertwo wants to merge 2 commits into
mainfrom
feature/discord-messengers
Open

feat(think): add multi-surface messenger ingress primitives#1776
dcartertwo wants to merge 2 commits into
mainfrom
feature/discord-messengers

Conversation

@dcartertwo

@dcartertwo dcartertwo commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

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

  • Add MessengerBackgroundIngress and MessengerBackgroundContext.
  • Allow messenger providers to disable HTTP route registration when they provide background ingress.
  • Add command and reaction messenger event kinds.
  • Add MessengerCommand and MessengerReaction context types.
  • Wire Chat SDK onSlashCommand() and onReaction() into Think messenger routing.
  • Generalize messenger delivery from Thread to Chat SDK postable surfaces so command replies can target channels.
  • Preserve provider command IDs across recovery snapshots.
  • Add validation to reject no-ingress messenger definitions.
  • Add tests for background ingress, command/reaction conversion, idempotency, recovery-stable command IDs, and route behavior.
  • Add a changeset for @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

  • Existing Telegram messenger behavior is unchanged.
  • Background ingress startup is root-agent only and retryable after startup failures.
  • The Discord RFC is design-only; this PR does not add the Discord provider yet.

Test plan

  • pnpm --filter @cloudflare/think run build
  • pnpm --filter @cloudflare/think exec vitest --run -c src/tests/vitest.config.ts src/tests/messengers.test.ts --reporter=dot
  • pnpm --filter @cloudflare/think exec vitest --run -c src/tests/vitest.config.ts --reporter=dot
  • pnpm run check

Open in Devin Review

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-bot

changeset-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 04da32d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/think Minor

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

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

Open in Devin Review

Comment on lines +445 to +446
chat.onReaction(async (event) => {
const thread = event.thread as ChatThread;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 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.

Suggested change
chat.onReaction(async (event) => {
const thread = event.thread as ChatThread;
chat.onReaction(async (event) => {
if (!event.thread) return;
const thread = event.thread as ChatThread;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +695 to +723
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 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().

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@pkg-pr-new

pkg-pr-new Bot commented Jun 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@1776

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@1776

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@1776

create-think

npm i https://pkg.pr.new/create-think@1776

hono-agents

npm i https://pkg.pr.new/hono-agents@1776

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@1776

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@1776

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@1776

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@1776

commit: 04da32d

@dcartertwo

Copy link
Copy Markdown
Collaborator Author

Addressed the AI review comments in 04da32d:

  • Added a defensive event.thread guard before casting in the reaction handler.
  • Documented the MessengerBackgroundIngress.start contract: the runtime retries startup failures, but a successful start means the provider owns durable reconnect/retry from there.

Re-verified with:

  • pnpm --filter @cloudflare/think exec vitest --run -c src/tests/vitest.config.ts src/tests/messengers.test.ts --reporter=dot
  • pnpm run check

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