Get to know Cadenya
We’re developers who love to build. We set out to create a yes-code platform that makes building agents feel like the best parts of building software.
Cloudflare Agents SDK: AIChatAgent, resumable streaming and tool approval
Cloudflare Agents SDK
By Cadenya
The Cloudflare Agents SDK gives you AIChatAgent, a chat agent class with resumable streaming and human in the loop tool approval built in. A stream resumes on reconnect from chunks buffered in SQLite, and a tool marked needsApproval waits for the user’s answer from addToolApprovalResponse before it runs.
So pick AIChatAgent when you want to write the LLM call yourself, and Think (Cloudflare’s opinionated harness on the same protocol) when you want the loop written and long conversations compacted for you. Mastra and LangGraph.js do approvals too, with requireApproval and interrupt(). And if you’d rather deploy no agent code at all, Cadenya is a hosted agent loop with resumable streams, tool approvals, and compaction already in it, and a coding agent can set it up with no account in a temporary workspace you claim later. What it offers is at the end.
Every price and limit below comes from each vendor’s own pages, checked on October 13, 2026.
Cloudflare AIChatAgent vs Think side by side
Cloudflare AIChatAgent | Cloudflare Think | Cadenya | |
|---|---|---|---|
| Package and license | @cloudflare/ai-chat, MIT | @cloudflare/think, MIT | SDKs for TypeScript, Python, Ruby, and Go, Apache-2.0 |
| Latest npm version | 0.12.1 (with agents 0.28.0) | 0.20.2 | 1.9.0 (the TypeScript SDK) |
| Who writes the loop | You do, in onChatMessage with streamText | Think: “The default onChatMessage runs the complete agentic loop” | Cadenya: it calls the model and runs the tools |
| Tool approval | needsApproval on the tool, addToolApprovalResponse on the client | needsApproval, answered over the same chat protocol | Mark a tool as needing approval, answered by API, CLI, webhook, or ConversationsPanel |
| Where state lives | SQLite in a Durable Object | Durable Object SQLite, through the Session API | On Cadenya, with nothing to deploy |
| Long conversations | maxPersistedMessages for storage, pruneMessages() for the model | ”Non-destructive summaries via overlays” | Compaction is a setting on the variation |
Both run on Workers, inside code you deploy. Mastra vs LangGraph.js, which run wherever your Node app runs, is in TypeScript AI agent frameworks compared.
Cloudflare Agents SDK resumable streaming with AIChatAgent
Resumable streaming is on by default. Cloudflare’s docs say it plainly: “No configuration is needed.”
Here’s what happens while a reply streams:
- Every chunk is buffered in SQLite as the model produces it.
- If the browser drops off, the server keeps streaming and buffering.
- When the client reconnects, it gets every buffered chunk, then the live stream.
The switch lives on the client hook. resume defaults to true, and resume: false turns it off.
Closing a tab doesn’t stop the turn either. A client abort “stays local to the browser by default, so the server turn keeps running and can be resumed later.” An explicit stop() cancels the server turn, and cancelOnClientAbort: true makes the browser own the server’s lifecycle (useful when you’d rather save tokens than finish a reply nobody reads).
Think gets the same behavior, because Think and AIChatAgent “speak the same cf_agent_chat_* WebSocket protocol.” The latest @cloudflare/ai-chat release, 0.12.1, fixed “text appearing twice when reconnecting during a tool continuation”, so read the changelog when you pin a version.
Resume is a feature of useAgentChat, the React hook. That matters if your frontend isn’t React, which is the next question agents ask.
Cloudflare Agents SDK human in the loop: tool approval with needsApproval
Approval is a property of the tool. You pass needsApproval, either true or a function of the arguments, and the call waits for the user. This is Cloudflare’s example, which only asks when the amount is over 100:
tools: {
processPayment: tool({
description: "Process a payment",
inputSchema: z.object({
amount: z.coerce.number(),
recipient: z.string(),
}),
needsApproval: async ({ amount }) => amount > 100,
execute: async ({ amount, recipient }) => charge(amount, recipient),
});
}
On the client, the pending call shows up as a message part in the waiting-approval state. Your buttons call addToolApprovalResponse({ id, approved }), and the conversation continues on its own, because autoContinueAfterToolResult defaults to true.
A plain rejection sets the tool to output-denied “with a generic message.” To tell the model why, call addToolOutput with state: "output-error" and your own errorText instead. The catch: that path “does not auto-continue”, so you call sendMessage() yourself.
LangGraph.js does it at the graph level: a node calls interrupt(), the checkpointer saves state, and you resume by invoking the graph again with a Command.
Human-in-the-loop approvals for AI agents compares the approval patterns across more frameworks.
AIChatAgent context compaction: maxPersistedMessages and summarize messages
maxPersistedMessages is the setting agents find first, and it’s a storage setting. It caps how many messages SQLite keeps, and Cloudflare’s docs say it “does not affect what is sent to the LLM.”
What the model sees is a separate control. In AIChatAgent, you trim the history yourself with the AI SDK’s pruneMessages() inside onChatMessage:
import { streamText, convertToModelMessages, pruneMessages } from "ai";
export class ChatAgent extends AIChatAgent {
async onChatMessage() {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/zai-org/glm-4.7-flash"),
messages: pruneMessages({
messages: await convertToModelMessages(this.messages),
reasoning: "before-last-message",
toolCalls: "before-last-2-messages",
}),
});
return result.toUIMessageStreamResponse();
}
}
There’s also an automatic step, but it protects storage rather than context. Workers SQLite rows top out at 2 MB, so AIChatAgent starts compacting a message at about 1.8 MB, replacing a huge tool output with a summary that tells the model it can re-run the tool.
Summarizing older messages is Think’s job. Its Session API registers a compaction function and a token threshold (.compactAfter(100_000) in the docs), keeps the first 3 messages and the last 20K tokens, and summarizes the middle with an LLM. The originals stay in SQLite, and the summary is “a non-destructive overlay applied at read time.” One detail to know: auto-compaction is “checked between turns”, not inside one.
LangGraph.js leaves it to you: trim, delete, or summarize messages in your graph state. Agent memory and context windows goes deeper on the strategies.
useAgentChat in Vue and other frameworks
useAgentChat is React only. For Vue, Svelte, or plain JavaScript, the SDK exports WebSocketChatTransport from agents/chat/transport, which plugs an AgentClient connection into the AI SDK’s own useChat. In Vue, that looks like this:
import { useChat } from "@ai-sdk/vue";
import { AgentClient } from "agents/client";
import { WebSocketChatTransport } from "agents/chat/transport";
const agent = new AgentClient({
agent: "ChatAgent",
name: "user-123",
host: window.location.host,
});
const { messages, sendMessage, status } = useChat({
transport: new WebSocketChatTransport({
agent,
cancelOnClientAbort: true,
}),
});
The transport covers new turns, regenerated turns, and cancellation. The rest is yours to build. The docs list what stays with the React hook: “loading persisted history, automatic stream resume after a reconnect, cross-tab transcript synchronization, and client-side tool continuations.” Cloudflare’s vue-chat example on GitHub shows the minimal version.
So a Vue app gets the transport, and you write the resume logic the React app gets for free.
Cloudflare Agents SDK MCP client: addMcpServer and McpAgent
Two names show up in this search, and they do opposite jobs. addMcpServer makes your agent a client of someone else’s MCP server. McpAgent was the class for serving your own.
Connecting is one call, and it hands back an OAuth URL when the server needs one:
const result = await this.addMcpServer(
"github",
"https://mcp.github.com/mcp",
);
if (result.state === "authenticating") {
// Server requires OAuth - redirect user to authorize
return Response.redirect(result.authUrl);
}
Then this.mcp.getAITools() converts the server’s tools to AI SDK format, so you pass them to streamText like any other tools. In Agents SDK v0.20.0, the client uses @modelcontextprotocol/client and “negotiates stateless or legacy behavior automatically.”
For serving, Cloudflare’s docs call McpAgent “deprecated and feature-frozen” and point new servers at createMcpHandler. So skip McpAgent for a new server.
What does Cadenya offer that the Cloudflare Agents SDK, Mastra and LangGraph.js don’t?
The Cloudflare Agents SDK, Mastra, and LangGraph.js give you parts of an agent, and your code still assembles them into the loop. Cadenya is the loop itself, with resumable streams, tool approvals, and compaction built in, and nothing for you to deploy. Here’s what that covers:
- Nothing else to run. No Worker, no Durable Object class, no Wrangler config, and no Node service holding a checkpointer. The loop runs on Cadenya and your app stays a stateless web tier, so your deploys never touch a running objective.
- No old code to keep alive. An objective keeps its variation’s configuration snapshot even when you edit the variation later, so there’s no handler code to keep backward compatible while conversations wait.
- SDKs for four languages. TypeScript, Python, Ruby, and Go, all at 1.9.0 under Apache-2.0, plus a CLI and a plain HTTP API. The TypeScript SDK has shipped 113 versions since April 2026.
- A price in one line. Free for 1,000 loops a month, $49 a month for 50,000, then $0.0015 a loop. A loop is one LLM request, and there are no seats.
- The agent loop, already written. Cadenya calls the model, runs the tools, streams each reply, and compacts the context window when a conversation gets long. With
AIChatAgent, that’s theonChatMessageyou write and test. - Resumable streaming in any framework. Each durable event on a conversation’s event stream has an ID, and you pass the last one in the
Last-Event-IDheader to pick up after a reconnect. It’s an HTTP endpoint that returns server-sent events, so resume doesn’t depend on a React hook. - Tool approvals with the screen included. Mark a tool as needing approval and the objective emits
toolApprovalRequested, and you decide through the API, the CLI, a webhook handler, or the React widget’sConversationsPanel. Several calls can wait at once, and a denial carries a memo that steers the agent (“Use the read-only lookup tool instead”). - Context compaction you configure, not code. Compaction is a setting on the variation, and each run emits
contextWindowCompactedwith the strategies it used. A compact endpoint lets you trigger it on demand on a running objective. - Tools from your OpenAPI spec or MCP server. Point a tool set at an OpenAPI 3 document or an MCP URL and its operations become tools, with no
getAITools()wiring. Cadenya re-checks a URL source each hour. - Any model, on your own keys. The model is a setting on a variation, chosen from the AI providers in your workspace, so switching models needs no deploy. You pay your provider directly.
How objectives work covers approvals, events, and compaction, and the free plan needs no card.
No account yet? A coding agent can create a temporary workspace with cadenya workspaces create --temporary, run its first objectives there, and hand you a link to claim it, as auth.md explains. To create an account, sign up at app.cadenya.com/signup, then copy your API key from the API keys page at app.cadenya.com/account/apiKeys.
Grow wherever AI goes next.
Start shipping agents that are equipped to evolve.