Skip to content

Changelog

New updates and improvements at Cloudflare.

Interactive browser terminals in Sandboxes

The Sandbox SDK now supports PTY (pseudo-terminal) passthrough, enabling browser-based terminal UIs to connect to sandbox shells via WebSocket.

sandbox.terminal(request)

The new terminal() method proxies a WebSocket upgrade to the container's PTY endpoint, with output buffering for replay on reconnect.

// Worker: proxy WebSocket to container terminal
return sandbox.terminal(request, { cols: 80, rows: 24 });
// Worker: proxy WebSocket to container terminal
return sandbox.terminal(request, { cols: 80, rows: 24 });

Multiple terminals per sandbox

Each session can have its own terminal with an isolated working directory and environment, so users can run separate shells side-by-side in the same container.

// Multiple isolated terminals in the same sandbox
const dev = await sandbox.getSession("dev");
return dev.terminal(request);
// Multiple isolated terminals in the same sandbox
const dev = await sandbox.getSession("dev");
return dev.terminal(request);

xterm.js addon

The new @cloudflare/sandbox/xterm export provides a SandboxAddon for xterm.js with automatic reconnection (exponential backoff + jitter), buffered output replay, and resize forwarding.

import { SandboxAddon } from "@cloudflare/sandbox/xterm";

const addon = new SandboxAddon({
	getWebSocketUrl: ({ sandboxId, origin }) =>
		`${origin}/ws/terminal?id=${sandboxId}`,
	onStateChange: (state, error) => updateUI(state),
});
terminal.loadAddon(addon);
addon.connect({ sandboxId: "my-sandbox" });
import { SandboxAddon } from "@cloudflare/sandbox/xterm";

const addon = new SandboxAddon({
	getWebSocketUrl: ({ sandboxId, origin }) =>
		`${origin}/ws/terminal?id=${sandboxId}`,
	onStateChange: (state, error) => updateUI(state),
});
terminal.loadAddon(addon);
addon.connect({ sandboxId: "my-sandbox" });

Upgrade

To update to the latest version:

npm i @cloudflare/sandbox@latest

Agents SDK v0.3.7: Workflows integration, synchronous state, and scheduleEvery()

The latest release of the Agents SDK brings first-class support for Cloudflare Workflows, synchronous state management, and new scheduling capabilities.

Cloudflare Workflows integration

Agents excel at real-time communication and state management. Workflows excel at durable execution. Together, they enable powerful patterns where Agents handle WebSocket connections while Workflows handle long-running tasks, retries, and human-in-the-loop flows.

Use the new AgentWorkflow class to define workflows with typed access to your Agent:

import { AgentWorkflow } from "agents/workflows";

export class ProcessingWorkflow extends AgentWorkflow {
	async run(event, step) {
		// Call Agent methods via RPC
		await this.agent.updateStatus(event.payload.taskId, "processing");

		// Non-durable: progress reporting to clients
		await this.reportProgress({ step: "process", percent: 0.5 });
		this.broadcastToClients({ type: "update", taskId: event.payload.taskId });

		// Durable via step: idempotent, won't repeat on retry
		await step.mergeAgentState({ taskProgress: 0.5 });

		const result = await step.do("process", async () => {
			return processData(event.payload.data);
		});

		await step.reportComplete(result);
		return result;
	}
}
import { AgentWorkflow } from "agents/workflows";
import type { AgentWorkflowEvent, AgentWorkflowStep } from "agents/workflows";

export class ProcessingWorkflow extends AgentWorkflow<MyAgent, TaskParams> {
	async run(event: AgentWorkflowEvent<TaskParams>, step: AgentWorkflowStep) {
		// Call Agent methods via RPC
		await this.agent.updateStatus(event.payload.taskId, "processing");

		// Non-durable: progress reporting to clients
		await this.reportProgress({ step: "process", percent: 0.5 });
		this.broadcastToClients({ type: "update", taskId: event.payload.taskId });

		// Durable via step: idempotent, won't repeat on retry
		await step.mergeAgentState({ taskProgress: 0.5 });

		const result = await step.do("process", async () => {
			return processData(event.payload.data);
		});

		await step.reportComplete(result);
		return result;
	}
}

Start workflows from your Agent with runWorkflow() and handle lifecycle events:

export class MyAgent extends Agent {
	async startTask(taskId, data) {
		const instanceId = await this.runWorkflow("PROCESSING_WORKFLOW", {
			taskId,
			data,
		});
		return { instanceId };
	}

	async onWorkflowProgress(workflowName, instanceId, progress) {
		this.broadcast(JSON.stringify({ type: "progress", progress }));
	}

	async onWorkflowComplete(workflowName, instanceId, result) {
		console.log(`Workflow ${instanceId} completed`);
	}

	async onWorkflowError(workflowName, instanceId, error) {
		console.error(`Workflow ${instanceId} failed:`, error);
	}
}
export class MyAgent extends Agent {
	async startTask(taskId: string, data: string) {
		const instanceId = await this.runWorkflow("PROCESSING_WORKFLOW", {
			taskId,
			data,
		});
		return { instanceId };
	}

	async onWorkflowProgress(
		workflowName: string,
		instanceId: string,
		progress: unknown,
	) {
		this.broadcast(JSON.stringify({ type: "progress", progress }));
	}

	async onWorkflowComplete(
		workflowName: string,
		instanceId: string,
		result?: unknown,
	) {
		console.log(`Workflow ${instanceId} completed`);
	}

	async onWorkflowError(
		workflowName: string,
		instanceId: string,
		error: unknown,
	) {
		console.error(`Workflow ${instanceId} failed:`, error);
	}
}

Key workflow methods on your Agent:

  • runWorkflow(workflowName, params, options?) — Start a workflow with optional metadata
  • getWorkflow(workflowId) / getWorkflows(criteria?) — Query workflows with cursor-based pagination
  • approveWorkflow(workflowId) / rejectWorkflow(workflowId) — Human-in-the-loop approval flows
  • pauseWorkflow(), resumeWorkflow(), terminateWorkflow() — Workflow control

Synchronous setState()

State updates are now synchronous with a new validateStateChange() validation hook:

export class MyAgent extends Agent {
	validateStateChange(oldState, newState) {
		// Return false to reject the change
		if (newState.count < 0) return false;
		// Return modified state to transform
		return { ...newState, lastUpdated: Date.now() };
	}
}
export class MyAgent extends Agent<Env, State> {
	validateStateChange(oldState: State, newState: State): State | false {
		// Return false to reject the change
		if (newState.count < 0) return false;
		// Return modified state to transform
		return { ...newState, lastUpdated: Date.now() };
	}
}

scheduleEvery() for recurring tasks

The new scheduleEvery() method enables fixed-interval recurring tasks with built-in overlap prevention:

// Run every 5 minutes
await this.scheduleEvery("syncData", 5 * 60 * 1000, { source: "api" });
// Run every 5 minutes
await this.scheduleEvery("syncData", 5 * 60 * 1000, { source: "api" });

Callable system improvements

  • Client-side RPC timeout — Set timeouts on callable method invocations
  • StreamingResponse.error(message) — Graceful stream error signaling
  • getCallableMethods() — Introspection API for discovering callable methods
  • Connection close handling — Pending calls are automatically rejected on disconnect
await agent.call("method", [args], {
	timeout: 5000,
	stream: { onChunk, onDone, onError },
});
await agent.call("method", [args], {
	timeout: 5000,
	stream: { onChunk, onDone, onError },
});

Email and routing enhancements

Secure email reply routing — Email replies are now secured with HMAC-SHA256 signed headers, preventing unauthorized routing of emails to agent instances.

Routing improvements:

  • basePath option to bypass default URL construction for custom routing
  • Server-sent identity — Agents send name and agent type on connect
  • New onIdentity and onIdentityChange callbacks on the client
const agent = useAgent({
	basePath: "user",
	onIdentity: (name, agentType) => console.log(`Connected to ${name}`),
});
const agent = useAgent({
	basePath: "user",
	onIdentity: (name, agentType) => console.log(`Connected to ${name}`),
});

Upgrade

To update to the latest version:

npm i agents@latest

For the complete Workflows API reference and patterns, see Run Workflows.

Agents SDK v0.3.0, workers-ai-provider v3.0.0, and ai-gateway-provider v3.0.0 with AI SDK v6 support

We've shipped a new release for the Agents SDK v0.3.0 bringing full compatibility with AI SDK v6 and introducing the unified tool pattern, dynamic tool approval, and enhanced React hooks with improved tool handling.

This release includes improved streaming and tool support, dynamic tool approval (for "human in the loop" systems), enhanced React hooks with onToolCall callback, improved error handling for streaming responses, and seamless migration from v5 patterns.

This makes it ideal for building production AI chat interfaces with Cloudflare Workers AI models, agent workflows, human-in-the-loop systems, or any application requiring reliable tool execution and approval workflows.

Additionally, we've updated workers-ai-provider v3.0.0, the official provider for Cloudflare Workers AI models, and ai-gateway-provider v3.0.0, the provider for Cloudflare AI Gateway, to be compatible with AI SDK v6.

Agents SDK v0.3.0

Unified Tool Pattern

AI SDK v6 introduces a unified tool pattern where all tools are defined on the server using the tool() function. This replaces the previous client-side AITool pattern.

Server-Side Tool Definition

import { tool } from "ai";
import { z } from "zod";

// Server: Define ALL tools on the server
const tools = {
	// Server-executed tool
	getWeather: tool({
		description: "Get weather for a city",
		inputSchema: z.object({ city: z.string() }),
		execute: async ({ city }) => fetchWeather(city)
	}),

	// Client-executed tool (no execute = client handles via onToolCall)
	getLocation: tool({
		description: "Get user location from browser",
		inputSchema: z.object({})
		// No execute function
	}),

	// Tool requiring approval (dynamic based on input)
	processPayment: tool({
		description: "Process a payment",
		inputSchema: z.object({ amount: z.number() }),
		needsApproval: async ({ amount }) => amount > 100,
		execute: async ({ amount }) => charge(amount)
	})
};

Client-Side Tool Handling

// Client: Handle client-side tools via onToolCall callback
import { useAgentChat } from "agents/ai-react";

const { messages, sendMessage, addToolOutput } = useAgentChat({
	agent,
	onToolCall: async ({ toolCall, addToolOutput }) => {
		if (toolCall.toolName === "getLocation") {
			const position = await new Promise((resolve, reject) => {
				navigator.geolocation.getCurrentPosition(resolve, reject);
			});
			addToolOutput({
				toolCallId: toolCall.toolCallId,
				output: {
					lat: position.coords.latitude,
					lng: position.coords.longitude
				}
			});
		}
	}
});

Key benefits of the unified tool pattern:

  • Server-defined tools: All tools are defined in one place on the server
  • Dynamic approval: Use needsApproval to conditionally require user confirmation
  • Cleaner client code: Use onToolCall callback instead of managing tool configs
  • Type safety: Full TypeScript support with proper tool typing

useAgentChat(options)

Creates a new chat interface with enhanced v6 capabilities.

// Basic chat setup with onToolCall
const { messages, sendMessage, addToolOutput } = useAgentChat({
	agent,
	onToolCall: async ({ toolCall, addToolOutput }) => {
		// Handle client-side tool execution
		await addToolOutput({
			toolCallId: toolCall.toolCallId,
			output: { result: "success" }
		});
	}
});

Dynamic Tool Approval

Use needsApproval on server tools to conditionally require user confirmation:

const paymentTool = tool({
	description: "Process a payment",
	inputSchema: z.object({
		amount: z.number(),
		recipient: z.string()
	}),
	needsApproval: async ({ amount }) => amount > 1000,
	execute: async ({ amount, recipient }) => {
		return await processPayment(amount, recipient);
	}
});

Tool Confirmation Detection

The isToolUIPart and getToolName functions now check both static and dynamic tool parts:

import { isToolUIPart, getToolName } from "ai";

const pendingToolCallConfirmation = messages.some((m) =>
	m.parts?.some(
		(part) => isToolUIPart(part) && part.state === "input-available",
	),
);

// Handle tool confirmation
if (pendingToolCallConfirmation) {
	await addToolOutput({
		toolCallId: part.toolCallId,
		output: "User approved the action"
	});
}

If you need the v5 behavior (static-only checks), use the new functions:

import { isStaticToolUIPart, getStaticToolName } from "ai";

convertToModelMessages() is now async

The convertToModelMessages() function is now asynchronous. Update all calls to await the result:

import { convertToModelMessages } from "ai";

const result = streamText({
	messages: await convertToModelMessages(this.messages),
	model: openai("gpt-4o")
});

ModelMessage type

The CoreMessage type has been removed. Use ModelMessage instead:

import { convertToModelMessages, type ModelMessage } from "ai";

const modelMessages: ModelMessage[] = await convertToModelMessages(messages);

generateObject mode option removed

The mode option for generateObject has been removed:

// Before (v5)
const result = await generateObject({
	mode: "json",
	model,
	schema,
	prompt
});

// After (v6)
const result = await generateObject({
	model,
	schema,
	prompt
});

Structured Output with generateText

While generateObject and streamObject are still functional, the recommended approach is to use generateText/streamText with the Output.object() helper:

import { generateText, Output, stepCountIs } from "ai";

const { output } = await generateText({
	model: openai("gpt-4"),
	output: Output.object({
		schema: z.object({ name: z.string() })
	}),
	stopWhen: stepCountIs(2),
	prompt: "Generate a name"
});

Note: When using structured output with generateText, you must configure multiple steps with stopWhen because generating the structured output is itself a step.

workers-ai-provider v3.0.0

Seamless integration with Cloudflare Workers AI models through the updated workers-ai-provider v3.0.0 with AI SDK v6 support.

Model Setup with Workers AI

Use Cloudflare Workers AI models directly in your agent workflows:

import { createWorkersAI } from "workers-ai-provider";
import { useAgentChat } from "agents/ai-react";

// Create Workers AI model (v3.0.0 - enhanced v6 internals)
const model = createWorkersAI({
	binding: env.AI,
})("@cf/meta/llama-3.2-3b-instruct");

Enhanced File and Image Support

Workers AI models now support v6 file handling with automatic conversion:

// Send images and files to Workers AI models
sendMessage({
	role: "user",
	parts: [
		{ type: "text", text: "Analyze this image:" },
		{
			type: "file",
			data: imageBuffer,
			mediaType: "image/jpeg",
		},
	],
});

// Workers AI provider automatically converts to proper format

Streaming with Workers AI

Enhanced streaming support with automatic warning detection:

// Streaming with Workers AI models
const result = await streamText({
	model: createWorkersAI({ binding: env.AI })("@cf/meta/llama-3.2-3b-instruct"),
	messages: await convertToModelMessages(messages),
	onChunk: (chunk) => {
		// Enhanced streaming with warning handling
		console.log(chunk);
	},
});

ai-gateway-provider v3.0.0

The ai-gateway-provider v3.0.0 now supports AI SDK v6, enabling you to use Cloudflare AI Gateway with multiple AI providers including Anthropic, Azure, AWS Bedrock, Google Vertex, and Perplexity.

AI Gateway Setup

Use Cloudflare AI Gateway to add analytics, caching, and rate limiting to your AI applications:

import { createAIGateway } from "ai-gateway-provider";

// Create AI Gateway provider (v3.0.0 - enhanced v6 internals)
const model = createAIGateway({
	gatewayUrl: "https://gateway.ai.cloudflare.com/v1/your-account-id/gateway",
	headers: {
		"Authorization": `Bearer ${env.AI_GATEWAY_TOKEN}`
	}
})({
	provider: "openai",
	model: "gpt-4o"
});

Migration from v5

Deprecated APIs

The following APIs are deprecated in favor of the unified tool pattern:

Deprecated Replacement
AITool type Use AI SDK's tool() function on server
extractClientToolSchemas() Define tools on server, no client schemas needed
createToolsFromClientSchemas() Define tools on server with tool()
toolsRequiringConfirmation option Use needsApproval on server tools
experimental_automaticToolResolution Use onToolCall callback
tools option in useAgentChat Use onToolCall for client-side execution
addToolResult() Use addToolOutput()

Breaking Changes Summary

  1. Unified Tool Pattern: All tools must be defined on the server using tool()
  2. convertToModelMessages() is async: Add await to all calls
  3. CoreMessage removed: Use ModelMessage instead
  4. generateObject mode removed: Remove mode option
  5. isToolUIPart behavior changed: Now checks both static and dynamic tool parts

Installation

Update your dependencies to use the latest versions:

npm install agents@^0.3.0 workers-ai-provider@^3.0.0 ai-gateway-provider@^3.0.0 ai@^6.0.0 @ai-sdk/react@^3.0.0 @ai-sdk/openai@^3.0.0

Resources

Feedback Welcome

We'd love your feedback! We're particularly interested in feedback on:

  • Migration experience - How smooth was the upgrade from v5 to v6?
  • Unified tool pattern - How does the new server-defined tool pattern work for you?
  • Dynamic tool approval - Does the needsApproval feature meet your needs?
  • AI Gateway integration - How well does the new provider work with your setup?

Agents SDK v0.2.24 with resumable streaming, MCP improvements, and schedule fixes

The latest release of @cloudflare/agents brings resumable streaming, significant MCP client improvements, and critical fixes for schedules and Durable Object lifecycle management.

Resumable streaming

AIChatAgent now supports resumable streaming, allowing clients to reconnect and continue receiving streamed responses without losing data. This is useful for:

  • Long-running AI responses
  • Users on unreliable networks
  • Users switching between devices mid-conversation
  • Background tasks where users navigate away and return
  • Real-time collaboration where multiple clients need to stay in sync

Streams are maintained across page refreshes, broken connections, and syncing across open tabs and devices.

Other improvements

  • Default JSON schema validator added to MCP client
  • Schedules can now safely destroy the agent

MCP client API improvements

The MCPClientManager API has been redesigned for better clarity and control:

  • New registerServer() method: Register MCP servers without immediately connecting
  • New connectToServer() method: Establish connections to registered servers
  • Improved reconnect logic: restoreConnectionsFromStorage() now properly handles failed connections
// Register a server to Agent
const { id } = await this.mcp.registerServer({
	name: "my-server",
	url: "https://my-mcp-server.example.com",
});

// Connect when ready
await this.mcp.connectToServer(id);

// Discover tools, prompts and resources
await this.mcp.discoverIfConnected(id);

The SDK now includes a formalized MCPConnectionState enum with states: idle, connecting, authenticating, connected, discovering, and ready.

Enhanced MCP discovery

MCP discovery fetches the available tools, prompts, and resources from an MCP server so your agent knows what capabilities are available. The MCPClientConnection class now includes a dedicated discover() method with improved reliability:

  • Supports cancellation via AbortController
  • Configurable timeout (default 15s)
  • Discovery failures now throw errors immediately instead of silently continuing

Bug fixes

  • Fixed a bug where schedules meant to fire immediately with this.schedule(0, ...) or this.schedule(new Date(), ...) would not fire
  • Fixed an issue where schedules that took longer than 30 seconds would occasionally time out
  • Fixed SSE transport now properly forwards session IDs and request headers
  • Fixed AI SDK stream events conversion to UIMessageStreamPart

Upgrade

To update to the latest version:

npm i agents@latest

Agents SDK v0.1.0 and workers-ai-provider v2.0.0 with AI SDK v5 support

We've shipped a new release for the Agents SDK bringing full compatibility with AI SDK v5 and introducing automatic message migration that handles all legacy formats transparently.

This release includes improved streaming and tool support, tool confirmation detection (for "human in the loop" systems), enhanced React hooks with automatic tool resolution, improved error handling for streaming responses, and seamless migration utilities that work behind the scenes.

This makes it ideal for building production AI chat interfaces with Cloudflare Workers AI models, agent workflows, human-in-the-loop systems, or any application requiring reliable message handling across SDK versions — all while maintaining backward compatibility.

Additionally, we've updated workers-ai-provider v2.0.0, the official provider for Cloudflare Workers AI models, to be compatible with AI SDK v5.

useAgentChat(options)

Creates a new chat interface with enhanced v5 capabilities.

// Basic chat setup
const { messages, sendMessage, addToolResult } = useAgentChat({
	agent,
	experimental_automaticToolResolution: true,
	tools,
});

// With custom tool confirmation
const chat = useAgentChat({
	agent,
	experimental_automaticToolResolution: true,
	toolsRequiringConfirmation: ["dangerousOperation"],
});

Automatic Tool Resolution

Tools are automatically categorized based on their configuration:

const tools = {
	// Auto-executes (has execute function)
	getLocalTime: {
		description: "Get current local time",
		inputSchema: z.object({}),
		execute: async () => new Date().toLocaleString(),
	},

	// Requires confirmation (no execute function)
	deleteFile: {
		description: "Delete a file from the system",
		inputSchema: z.object({
			filename: z.string(),
		}),
	},

	// Server-executed (no client confirmation)
	analyzeData: {
		description: "Analyze dataset on server",
		inputSchema: z.object({ data: z.array(z.number()) }),
		serverExecuted: true,
	},
} satisfies Record<string, AITool>;

Message Handling

Send messages using the new v5 format with parts array:

// Text message
sendMessage({
	role: "user",
	parts: [{ type: "text", text: "Hello, assistant!" }],
});

// Multi-part message with file
sendMessage({
	role: "user",
	parts: [
		{ type: "text", text: "Analyze this image:" },
		{ type: "image", image: imageData },
	],
});

Tool Confirmation Detection

Simplified logic for detecting pending tool confirmations:

const pendingToolCallConfirmation = messages.some((m) =>
	m.parts?.some(
		(part) => isToolUIPart(part) && part.state === "input-available",
	),
);

// Handle tool confirmation
if (pendingToolCallConfirmation) {
	await addToolResult({
		toolCallId: part.toolCallId,
		tool: getToolName(part),
		output: "User approved the action",
	});
}

Automatic Message Migration

Seamlessly handle legacy message formats without code changes.

// All these formats are automatically converted:

// Legacy v4 string content
const legacyMessage = {
	role: "user",
	content: "Hello world",
};

// Legacy v4 with tool calls
const legacyWithTools = {
	role: "assistant",
	content: "",
	toolInvocations: [
		{
			toolCallId: "123",
			toolName: "weather",
			args: { city: "SF" },
			state: "result",
			result: "Sunny, 72°F",
		},
	],
};

// Automatically becomes v5 format:
// {
//   role: "assistant",
//   parts: [{
//     type: "tool-call",
//     toolCallId: "123",
//     toolName: "weather",
//     args: { city: "SF" },
//     state: "result",
//     result: "Sunny, 72°F"
//   }]
// }

Tool Definition Updates

Migrate tool definitions to use the new inputSchema property.

// Before (AI SDK v4)
const tools = {
	weather: {
		description: "Get weather information",
		parameters: z.object({
			city: z.string(),
		}),
		execute: async (args) => {
			return await getWeather(args.city);
		},
	},
};

// After (AI SDK v5)
const tools = {
	weather: {
		description: "Get weather information",
		inputSchema: z.object({
			city: z.string(),
		}),
		execute: async (args) => {
			return await getWeather(args.city);
		},
	},
};

Cloudflare Workers AI Integration

Seamless integration with Cloudflare Workers AI models through the updated workers-ai-provider v2.0.0.

Model Setup with Workers AI

Use Cloudflare Workers AI models directly in your agent workflows:

import { createWorkersAI } from "workers-ai-provider";
import { useAgentChat } from "agents/ai-react";

// Create Workers AI model (v2.0.0 - same API, enhanced v5 internals)
const model = createWorkersAI({
	binding: env.AI,
})("@cf/meta/llama-3.2-3b-instruct");

Enhanced File and Image Support

Workers AI models now support v5 file handling with automatic conversion:

// Send images and files to Workers AI models
sendMessage({
	role: "user",
	parts: [
		{ type: "text", text: "Analyze this image:" },
		{
			type: "file",
			data: imageBuffer,
			mediaType: "image/jpeg",
		},
	],
});

// Workers AI provider automatically converts to proper format

Streaming with Workers AI

Enhanced streaming support with automatic warning detection:

// Streaming with Workers AI models
const result = await streamText({
	model: createWorkersAI({ binding: env.AI })("@cf/meta/llama-3.2-3b-instruct"),
	messages,
	onChunk: (chunk) => {
		// Enhanced streaming with warning handling
		console.log(chunk);
	},
});

Import Updates

Update your imports to use the new v5 types:

// Before (AI SDK v4)
import type { Message } from "ai";
import { useChat } from "ai/react";

// After (AI SDK v5)
import type { UIMessage } from "ai";
// or alias for compatibility
import type { UIMessage as Message } from "ai";
import { useChat } from "@ai-sdk/react";

Resources

Feedback Welcome

We'd love your feedback! We're particularly interested in feedback on:

  • Migration experience - How smooth was the upgrade process?
  • Tool confirmation workflow - Does the new automatic detection work as expected?
  • Message format handling - Any edge cases with legacy message conversion?

Agents SDK adds MCP Elicitation support, http-streamable support, task queues, email integration and more

The latest releases of @cloudflare/agents brings major improvements to MCP transport protocols support and agents connectivity. Key updates include:

MCP elicitation support

MCP servers can now request user input during tool execution, enabling interactive workflows like confirmations, forms, and multi-step processes. This feature uses durable storage to preserve elicitation state even during agent hibernation, ensuring seamless user interactions across agent lifecycle events.

// Request user confirmation via elicitation
const confirmation = await this.elicitInput({
	message: `Are you sure you want to increment the counter by ${amount}?`,
	requestedSchema: {
		type: "object",
		properties: {
			confirmed: {
				type: "boolean",
				title: "Confirm increment",
				description: "Check to confirm the increment",
			},
		},
		required: ["confirmed"],
	},
});

Check out our demo to see elicitation in action.

HTTP streamable transport for MCP

MCP now supports HTTP streamable transport which is recommended over SSE. This transport type offers:

  • Better performance: More efficient data streaming and reduced overhead
  • Improved reliability: Enhanced connection stability and error recover- Automatic fallback: If streamable transport is not available, it gracefully falls back to SSE
export default MyMCP.serve("/mcp", {
	binding: "MyMCP",
});

The SDK automatically selects the best available transport method, gracefully falling back from streamable-http to SSE when needed.

Enhanced MCP connectivity

Significant improvements to MCP server connections and transport reliability:

  • Auto transport selection: Automatically determines the best transport method, falling back from streamable-http to SSE as needed
  • Improved error handling: Better connection state management and error reporting for MCP servers
  • Reliable prop updates: Centralized agent property updates ensure consistency across different contexts

Lightweight .queue for fast task deferral

You can use .queue() to enqueue background work — ideal for tasks like processing user messages, sending notifications etc.

class MyAgent extends Agent {
	doSomethingExpensive(payload) {
		// a long running process that you want to run in the background
	}

	queueSomething() {
		await this.queue("doSomethingExpensive", somePayload); // this will NOT block further execution, and runs in the background
		await this.queue("doSomethingExpensive", someOtherPayload); // the callback will NOT run until the previous callback is complete
		// ... call as many times as you want
	}
}

Want to try it yourself? Just define a method like processMessage in your agent, and you’re ready to scale.

New email adapter

Want to build an AI agent that can receive and respond to emails automatically? With the new email adapter and onEmail lifecycle method, now you can.

export class EmailAgent extends Agent {
	async onEmail(email: AgentEmail) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		// create a response based on the email contents
		// and then send a reply

		await this.replyToEmail(email, {
			fromName: "Email Agent",
			body: `Thanks for your email! You've sent us "${parsed.subject}". We'll process it shortly.`,
		});
	}
}

You route incoming mail like this:

export default {
	async email(email, env) {
		await routeAgentEmail(email, env, {
			resolver: createAddressBasedEmailResolver("EmailAgent"),
		});
	},
};

You can find a full example here.

Automatic context wrapping for custom methods

Custom methods are now automatically wrapped with the agent's context, so calling getCurrentAgent() should work regardless of where in an agent's lifecycle it's called. Previously this would not work on RPC calls, but now just works out of the box.

export class MyAgent extends Agent {
	async suggestReply(message) {
		// getCurrentAgent() now correctly works, even when called inside an RPC method
		const { agent } = getCurrentAgent()!;
		return generateText({
			prompt: `Suggest a reply to: "${message}" from "${agent.name}"`,
			tools: [replyWithEmoji],
		});
	}
}

Try it out and tell us what you build!

Cloudflare Sandbox SDK adds streaming, code interpreter, Git support, process control and more

We’ve shipped a major release for the @cloudflare/sandbox SDK, turning it into a full-featured, container-based execution platform that runs securely on Cloudflare Workers.

This update adds live streaming of output, persistent Python and JavaScript code interpreters with rich output support (charts, tables, HTML, JSON), file system access, Git operations, full background process control, and the ability to expose running services via public URLs.

This makes it ideal for building AI agents, CI runners, cloud REPLs, data analysis pipelines, or full developer tools — all without managing infrastructure.

Code interpreter (Python, JS, TS)

Create persistent code contexts with support for rich visual + structured outputs.

createCodeContext(options)

Creates a new code execution context with persistent state.

// Create a Python context
const pythonCtx = await sandbox.createCodeContext({ language: "python" });

// Create a JavaScript context
const jsCtx = await sandbox.createCodeContext({ language: "javascript" });

Options:

  • language: Programming language ('python' | 'javascript' | 'typescript')
  • cwd: Working directory (default: /workspace)
  • envVars: Environment variables for the context

runCode(code, options)

Executes code with optional streaming callbacks.

// Simple execution
const execution = await sandbox.runCode('print("Hello World")', {
	context: pythonCtx,
});

// With streaming callbacks
await sandbox.runCode(
	`
for i in range(5):
    print(f"Step {i}")
    time.sleep(1)
`,
	{
		context: pythonCtx,
		onStdout: (output) => console.log("Real-time:", output.text),
		onResult: (result) => console.log("Result:", result),
	},
);

Options:

  • language: Programming language ('python' | 'javascript' | 'typescript')
  • cwd: Working directory (default: /workspace)
  • envVars: Environment variables for the context

Real-time streaming output

Returns a streaming response for real-time processing.

const stream = await sandbox.runCodeStream(
	"import time; [print(i) for i in range(10)]",
);
// Process the stream as needed

Rich output handling

Interpreter outputs are auto-formatted and returned in multiple formats:

  • text
  • html (e.g., Pandas tables)
  • png, svg (e.g., Matplotlib charts)
  • json (structured data)
  • chart (parsed visualizations)
const result = await sandbox.runCode(
	`
import seaborn as sns
import matplotlib.pyplot as plt

data = sns.load_dataset("flights")
pivot = data.pivot("month", "year", "passengers")
sns.heatmap(pivot, annot=True, fmt="d")
plt.title("Flight Passengers")
plt.show()

pivot.to_dict()
`,
	{ context: pythonCtx },
);

if (result.png) {
	console.log("Chart output:", result.png);
}

Preview URLs from Exposed Ports

Start background processes and expose them with live URLs.

await sandbox.startProcess("python -m http.server 8000");
const preview = await sandbox.exposePort(8000);

console.log("Live preview at:", preview.url);

Full process lifecycle control

Start, inspect, and terminate long-running background processes.

const process = await sandbox.startProcess("node server.js");
console.log(`Started process ${process.id} with PID ${process.pid}`);

// Monitor the process
const logStream = await sandbox.streamProcessLogs(process.id);
for await (const log of parseSSEStream<LogEvent>(logStream)) {
	console.log(`Server: ${log.data}`);
}
  • listProcesses() - List all running processes
  • getProcess(id) - Get detailed process status
  • killProcess(id, signal) - Terminate specific processes
  • killAllProcesses() - Kill all processes
  • streamProcessLogs(id, options) - Stream logs from running processes
  • getProcessLogs(id) - Get accumulated process output

Git integration

Clone Git repositories directly into the sandbox.

await sandbox.gitCheckout("https://github.com/user/repo", {
	branch: "main",
	targetDir: "my-project",
});

Sandboxes are still experimental. We're using them to explore how isolated, container-like workloads might scale on Cloudflare — and to help define the developer experience around them.

OpenAI open models now available on Workers AI

We're thrilled to be a Day 0 partner with OpenAI to bring their latest open models to Workers AI, including support for Responses API, Code Interpreter, and Web Search (coming soon).

Get started with the new models at @cf/openai/gpt-oss-120b and @cf/openai/gpt-oss-20b. Check out the blog for more details about the new models, and the gpt-oss-120b and gpt-oss-20b model pages for more information about pricing and context windows.

Responses API

If you call the model through:

  • Workers Binding, it will accept/return Responses API – env.AI.run(“@cf/openai/gpt-oss-120b”)
  • REST API on /run endpoint, it will accept/return Responses API – https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/run/@cf/openai/gpt-oss-120b
  • REST API on new /responses endpoint, it will accept/return Responses API – https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/v1/responses
  • REST API for OpenAI Compatible endpoint, it will return Chat Completions (coming soon) – https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/v1/chat/completions
curl https://api.cloudflare.com/client/v4/accounts/<account_id>/ai/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $CLOUDFLARE_API_KEY" \
  -d '{
    "model": "@cf/openai/gpt-oss-120b",
    "reasoning": {"effort": "medium"},
    "input": [
      {
        "role": "user",
        "content": "What are the benefits of open-source models?"
      }
    ]
  }'

Code Interpreter

The model is natively trained to support stateful code execution, and we've implemented support for this feature using our Sandbox SDK and Containers. Cloudflare's Developer Platform is uniquely positioned to support this feature, so we're very excited to bring our products together to support this new use case.

Web Search (coming soon)

We are working to implement Web Search for the model, where users can bring their own Exa API Key so the model can browse the Internet.

Run AI-generated code on-demand with Code Sandboxes (new)

AI is supercharging app development for everyone, but we need a safe way to run untrusted, LLM-written code. We’re introducing Sandboxes, which let your Worker run actual processes in a secure, container-based environment.

import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";

export default {
	async fetch(request: Request, env: Env) {
		const sandbox = getSandbox(env.Sandbox, "my-sandbox");
		return sandbox.exec("ls", ["-la"]);
	},
};

Methods

  • exec(command: string, args: string[], options?: { stream?: boolean }):Execute a command in the sandbox.
  • gitCheckout(repoUrl: string, options: { branch?: string; targetDir?: string; stream?: boolean }): Checkout a git repository in the sandbox.
  • mkdir(path: string, options: { recursive?: boolean; stream?: boolean }): Create a directory in the sandbox.
  • writeFile(path: string, content: string, options: { encoding?: string; stream?: boolean }): Write content to a file in the sandbox.
  • readFile(path: string, options: { encoding?: string; stream?: boolean }): Read content from a file in the sandbox.
  • deleteFile(path: string, options?: { stream?: boolean }): Delete a file from the sandbox.
  • renameFile(oldPath: string, newPath: string, options?: { stream?: boolean }): Rename a file in the sandbox.
  • moveFile(sourcePath: string, destinationPath: string, options?: { stream?: boolean }): Move a file from one location to another in the sandbox.
  • ping(): Ping the sandbox.

Sandboxes are still experimental. We're using them to explore how isolated, container-like workloads might scale on Cloudflare — and to help define the developer experience around them.

You can try it today from your Worker, with just a few lines of code. Let us know what you build.

Build MCP servers with the Agents SDK

The Agents SDK now includes built-in support for building remote MCP (Model Context Protocol) servers directly as part of your Agent. This allows you to easily create and manage MCP servers, without the need for additional infrastructure or configuration.

The SDK includes a new MCPAgent class that extends the Agent class and allows you to expose resources and tools over the MCP protocol, as well as authorization and authentication to enable remote MCP servers.

export class MyMCP extends McpAgent {
	server = new McpServer({
		name: "Demo",
		version: "1.0.0",
	});

	async init() {
		this.server.resource(`counter`, `mcp://resource/counter`, (uri) => {
			// ...
		});

		this.server.tool(
			"add",
			"Add two numbers together",
			{ a: z.number(), b: z.number() },
			async ({ a, b }) => {
				// ...
			},
		);
	}
}
export class MyMCP extends McpAgent<Env> {
	server = new McpServer({
		name: "Demo",
		version: "1.0.0",
	});

	async init() {
		this.server.resource(`counter`, `mcp://resource/counter`, (uri) => {
			// ...
		});

		this.server.tool(
			"add",
			"Add two numbers together",
			{ a: z.number(), b: z.number() },
			async ({ a, b }) => {
				// ...
			},
		);
	}
}

See the example for the full code and as the basis for building your own MCP servers, and the client example for how to build an Agent that acts as an MCP client.

To learn more, review the announcement blog as part of Developer Week 2025.

Agents SDK updates

We've made a number of improvements to the Agents SDK, including:

  • Support for building MCP servers with the new MCPAgent class.
  • The ability to export the current agent, request and WebSocket connection context using import { context } from "agents", allowing you to minimize or avoid direct dependency injection when calling tools.
  • Fixed a bug that prevented query parameters from being sent to the Agent server from the useAgent React hook.
  • Automatically converting the agent name in useAgent or useAgentChat to kebab-case to ensure it matches the naming convention expected by routeAgentRequest.

To install or update the Agents SDK, run npm i agents@latest in an existing project, or explore the agents-starter project:

npm create cloudflare@latest -- --template cloudflare/agents-starter

See the full release notes and changelog on the Agents SDK repository and

npm i agents

npm i agents

agents-sdk -> agents Updated

📝 We've renamed the Agents package to agents!

If you've already been building with the Agents SDK, you can update your dependencies to use the new package name, and replace references to agents-sdk with agents:

# Install the new package
npm i agents
# Remove the old (deprecated) package
npm uninstall agents-sdk

# Find instances of the old package name in your codebase
grep -r 'agents-sdk' .
# Replace instances of the old package name with the new one
# (or use find-replace in your editor)
sed -i 's/agents-sdk/agents/g' $(grep -rl 'agents-sdk' .)

All future updates will be pushed to the new agents package, and the older package has been marked as deprecated.

Agents SDK updates New

We've added a number of big new features to the Agents SDK over the past few weeks, including:

  • You can now set cors: true when using routeAgentRequest to return permissive default CORS headers to Agent responses.
  • The regular client now syncs state on the agent (just like the React version).
  • useAgentChat bug fixes for passing headers/credentials, including properly clearing cache on unmount.
  • Experimental /schedule module with a prompt/schema for adding scheduling to your app (with evals!).
  • Changed the internal zod schema to be compatible with the limitations of Google's Gemini models by removing the discriminated union, allowing you to use Gemini models with the scheduling API.

We've also fixed a number of bugs with state synchronization and the React hooks.

// via https://github.com/cloudflare/agents/tree/main/examples/cross-domain
export default {
	async fetch(request, env) {
		return (
			// Set { cors: true } to enable CORS headers.
			(await routeAgentRequest(request, env, { cors: true })) ||
			new Response("Not found", { status: 404 })
		);
	},
};
// via https://github.com/cloudflare/agents/tree/main/examples/cross-domain
export default {
	async fetch(request: Request, env: Env) {
		return (
			// Set { cors: true } to enable CORS headers.
			(await routeAgentRequest(request, env, { cors: true })) ||
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

Call Agent methods from your client code New

We've added a new @unstable_callable() decorator for defining methods that can be called directly from clients. This allows you call methods from within your client code: you can call methods (with arguments) and get native JavaScript objects back.

// server.ts
import { unstable_callable, Agent } from "agents";

export class Rpc extends Agent {
	// Use the decorator to define a callable method
	@unstable_callable({
		description: "rpc test",
	})
	async getHistory() {
		return this.sql`SELECT * FROM history ORDER BY created_at DESC LIMIT 10`;
	}
}
// server.ts
import { unstable_callable, Agent, type StreamingResponse } from "agents";
import type { Env } from "../server";

export class Rpc extends Agent<Env> {
	// Use the decorator to define a callable method
	@unstable_callable({
		description: "rpc test",
	})
	async getHistory() {
		return this.sql`SELECT * FROM history ORDER BY created_at DESC LIMIT 10`;
	}
}
// client.tsx
const { call } = useAgent({ agent: "rpc" });

const fetchUserHistory = async () => {
	try {
		setLoading(true);
		// Call methods directly on the Agent!
		const result = await call("getHistory");
		addToast(`RPC result: ${result}`, "success");
	} catch (error) {
		addToast(`Error: ${error}`, "error");
	} finally {
		setLoading(false);
	}
};

agents-starter Updated

We've fixed a number of small bugs in the agents-starter project — a real-time, chat-based example application with tool-calling & human-in-the-loop built using the Agents SDK. The starter has also been upgraded to use the latest wrangler v4 release.

If you're new to Agents, you can install and run the agents-starter project in two commands:

# Install it
$ npm create cloudflare@latest agents-starter -- --template="cloudflare/agents-starter"
# Run it
$ npm run start

You can use the starter as a template for your own Agents projects: open up src/server.ts and src/client.tsx to see how the Agents SDK is used.

More documentation Updated

We've heard your feedback on the Agents SDK documentation, and we're shipping more API reference material and usage examples, including:

  • Expanded API reference documentation, covering the methods and properties exposed by the Agents SDK, as well as more usage examples.
  • More Client API documentation that documents useAgent, useAgentChat and the new @unstable_callable RPC decorator exposed by the SDK.
  • New documentation on how to route requests to agents and (optionally) authenticate clients before they connect to your Agents.

Note that the Agents SDK is continually growing: the type definitions included in the SDK will always include the latest APIs exposed by the agents package.

If you're still wondering what Agents are, read our blog on building AI Agents on Cloudflare and/or visit the Agents documentation to learn more.

Introducing the Agents SDK

We've released the Agents SDK, a package and set of tools that help you build and ship AI Agents.

You can get up and running with a chat-based AI Agent (and deploy it to Workers) that uses the Agents SDK, tool calling, and state syncing with a React-based front-end by running the following command:

npm create cloudflare@latest agents-starter -- --template="cloudflare/agents-starter"
# open up README.md and follow the instructions

You can also add an Agent to any existing Workers application by installing the agents package directly

npm i agents

... and then define your first Agent:

import { Agent } from "agents";

export class YourAgent extends Agent<Env> {
	// Build it out
	// Access state on this.state or query the Agent's database via this.sql
	// Handle WebSocket events with onConnect and onMessage
	// Run tasks on a schedule with this.schedule
	// Call AI models
	// ... and/or call other Agents.
}

Head over to the Agents documentation to learn more about the Agents SDK, the SDK APIs, as well as how to test and deploying agents to production.

Build AI Agents with Example Prompts

We've added an example prompt to help you get started with building AI agents and applications on Cloudflare Workers, including Workflows, Durable Objects, and Workers KV.

You can use this prompt with your favorite AI model, including Claude 3.5 Sonnet, OpenAI's o3-mini, Gemini 2.0 Flash, or Llama 3.3 on Workers AI. Models with large context windows will allow you to paste the prompt directly: provide your own prompt within the <user_prompt></user_prompt> tags.

{paste_prompt_here}
<user_prompt>
user: Build an AI agent using Cloudflare Workflows. The Workflow should run when a new GitHub issue is opened on a specific project with the label 'help' or 'bug', and attempt to help the user troubleshoot the issue by calling the OpenAI API with the issue title and description, and a clear, structured prompt that asks the model to suggest 1-3 possible solutions to the issue. Any code snippets should be formatted in Markdown code blocks. Documentation and sources should be referenced at the bottom of the response. The agent should then post the response to the GitHub issue. The agent should run as the provided GitHub bot account.
</user_prompt>

This prompt is still experimental, but we encourage you to try it out and provide feedback.