feat(agents): add experimental outbound channels - #2086
Conversation
🦋 Changeset detectedLatest commit: 75b28e1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
| async deliver(message) { | ||
| const rendered = renderMarkdown(message.markdown); | ||
| if (rendered.text === undefined && rendered.html === undefined) { | ||
| throw new Error("renderMarkdown must return text or html content"); | ||
| } |
There was a problem hiding this comment.
🟡 Email channel throws an error instead of reporting a failed delivery when the message body comes out empty
The email route raises an exception (throw new Error at packages/agents/src/experimental/channels/email.ts:116) outside the protected send path when the message body renders to nothing, so callers and models get a crash instead of the promised delivery outcome.
Impact: A tool call that should report a clean "failed" outcome instead blows up the surrounding agent turn.
Contract mismatch between deliver() and its documented DeliveryResult outcomes
Channel.deliver is declared to always resolve to a DeliveryResult (packages/agents/src/experimental/channels/channel.ts:41-43), and createChannelTool wires deliver straight into the AI SDK tool's execute (packages/agents/src/experimental/channels/channel.ts:78). In email.ts the guard on the rendered projection, and any exception thrown by a caller-supplied renderMarkdown, happen before the try block at packages/agents/src/experimental/channels/email.ts:119, so they reject the promise rather than returning { status: "failed", retryable: false, ... }. Only binding errors are classified. Moving the render call and the emptiness check inside the try (or returning a failed result) would keep the contract consistent.
| async deliver(message) { | |
| const rendered = renderMarkdown(message.markdown); | |
| if (rendered.text === undefined && rendered.html === undefined) { | |
| throw new Error("renderMarkdown must return text or html content"); | |
| } | |
| async deliver(message) { | |
| let rendered: RenderedEmailMarkdown; | |
| try { | |
| rendered = renderMarkdown(message.markdown); | |
| if (rendered.text === undefined && rendered.html === undefined) { | |
| throw new Error("renderMarkdown must return text or html content"); | |
| } | |
| } catch (error) { | |
| return { | |
| status: "failed", | |
| retryable: false, | |
| error: emailFailure(error) | |
| }; | |
| } |
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: |
This PR adds an experimental transport-neutral outbound Channels API at
agents/experimental/channels, with an Email Service adapter and an AI SDK tool bridge. Think is an integration consumer, but it does not appear in the Channels contract.Why
titleplus Markdown content, gives adapters one semantic payload to project into transport-specific fields.delivered, safely retryable or permanentfailed, anduncertainoutcomes lets callers avoid blindly duplicating messages.agentsentry point keeps the first slice small while the abstraction is validated.Not Included
This is deliberately narrower than the broader Channels direction discussed previously. At this stage it is a tool wrapper around one configured outbound transport.
Channel. The model must explicitly call a tool created withcreateChannelTool()for anything to be sent.Public API Surface
All additions are exported from
agents/experimental/channels.ChannelMessageDeliveryFailureDeliveryResultChanneldeliver()CreateChannelToolOptionscreateChannelTool()Channelto an AI SDK toolRenderedEmailMarkdownEmailChannelOptionsemail()ChannelThe existing
EmailSendBindingexport remains available under the same name and now aliases Cloudflare's platformSendEmailtype. The existingSendEmailOptionsshape is preserved.Architectural Changes
ToolSet, so the Channels module does not prescribe product behavior.Code Changes
titleto subject, renders Markdown as text by default, supports caller-provided text and HTML rendering, and conservatively classifies ambiguous failures asuncertain.Agent.sendEmail()message construction into an internal email helper while preserving Agent routing headers, signing, observability, error handling, and the existing public method.agents.