跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

了解运行时

最后更新 查看 MarkdownAgent 设置

Cloudflare 上的 Agent 运行在 Durable Object 中。Agents SDK Lifecycle 在该 Durable Object 内部运行,并启动你添加的功能。Harness(例如构建你的第一个 Durable Agent中的 Pi harness)就是其中一种功能。

本指南不包含 harness,以便你单独了解运行时。你将在 Durable Object 上构建一个计数器,并添加 State 和 WebSockets 两项功能。然后通过 HTTP 调用它,并连接一个通过 WebSockets 保持同步的客户端。这里介绍的内容同样适用于同时运行 harness 的对象。

先决条件

创建项目

  1. 创建一个 Worker 项目:

    npm create cloudflare@latest -- my-agent --type=hello-world --lang=ts
  2. 前往项目目录并安装 Agents SDK:

    cd my-agent
    npm i agents

配置 Wrangler

在 Wrangler 配置文件中添加一个由 SQLite 支持的 Durable Object:

{
	"name": "my-agent",
	"main": "src/index.ts",
	"upload_source_maps": true,
	// Set this to today's date
	"compatibility_date": "2026-11-14",
	"compatibility_flags": ["nodejs_compat"],
	"durable_objects": {
		"bindings": [{ "name": "Counter", "class_name": "Counter" }],
	},
	"exports": {
		"Counter": { "type": "durable-object", "storage": "sqlite" },
	},
}
name = "my-agent"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-11-14"
compatibility_flags = [ "nodejs_compat" ]

[[durable_objects.bindings]]
name = "Counter"
class_name = "Counter"

[exports.Counter]
type = "durable-object"
storage = "sqlite"
  • 绑定中的 name 会成为 env 上的属性,例如 env.Counter。
  • class_name 必须与导出的类名一致。
  • exports 使用 "storage": "sqlite" 声明该类。生命周期及其功能将数据存储在每个对象的 SQLite 数据库中。请参阅 Durable Object 类导出。
  • agents 软件包需要 nodejs_compat 标志。

编写 Agent

替换 src/index.ts:

src/index.jsjs
import { DurableObject, RpcTarget } from "cloudflare:workers";
import { routeAgentRequest } from "agents";
import { Lifecycle } from "agents/lifecycle";
import { State } from "agents/state";
import { WebSockets } from "agents/websockets";

export class Counter extends DurableObject {
	// Durable state, stored in the object's SQLite database
	state = new State({
		initialState: { count: 0 },
		onChanged: (_state, source) => this.webSockets.broadcastState(source),
	});

	// WebSocket connections. Each client receives the state, and can
	// call the methods on CounterMethods.
	webSockets = new WebSockets({
		state: this.state,
		callables: new CounterMethods(this),
	});

	// The lifecycle starts each capability inside the Durable Object
	lifecycle = Lifecycle.install(this).use(this.state).use(this.webSockets);

	increment() {
		const count = (this.state.get()?.count ?? 0) + 1;
		this.state.set({ count });
		return count;
	}

	onRequest(request) {
		if (request.method === "POST") {
			return Response.json({ count: this.increment() });
		}
		return Response.json(this.state.get());
	}
}

// The methods that clients can call over a WebSocket
class CounterMethods extends RpcTarget {
	#counter;

	constructor(counter) {
		super();
		this.#counter = counter;
	}

	increment() {
		return this.#counter.increment();
	}

	reset() {
		this.#counter.state.set({ count: 0 });
	}
}

export default {
	async fetch(request, env) {
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
};
src/index.tsts
import { DurableObject, RpcTarget } from "cloudflare:workers";
import { routeAgentRequest } from "agents";
import { Lifecycle } from "agents/lifecycle";
import { State } from "agents/state";
import { WebSockets } from "agents/websockets";

export type CounterState = { count: number };

export class Counter extends DurableObject<Env> {
	// Durable state, stored in the object's SQLite database
	readonly state: State<CounterState> = new State({
		initialState: { count: 0 },
		onChanged: (_state, source) => this.webSockets.broadcastState(source),
	});

	// WebSocket connections. Each client receives the state, and can
	// call the methods on CounterMethods.
	readonly webSockets: WebSockets = new WebSockets({
		state: this.state,
		callables: new CounterMethods(this),
	});

	// The lifecycle starts each capability inside the Durable Object
	readonly lifecycle = Lifecycle.install(this)
		.use(this.state)
		.use(this.webSockets);

	increment(): number {
		const count = (this.state.get()?.count ?? 0) + 1;
		this.state.set({ count });
		return count;
	}

	onRequest(request: Request): Response {
		if (request.method === "POST") {
			return Response.json({ count: this.increment() });
		}
		return Response.json(this.state.get());
	}
}

// The methods that clients can call over a WebSocket
class CounterMethods extends RpcTarget {
	readonly #counter: Counter;

	constructor(counter: Counter) {
		super();
		this.#counter = counter;
	}

	increment(): number {
		return this.#counter.increment();
	}

	reset(): void {
		this.#counter.state.set({ count: 0 });
	}
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		return (
			(await routeAgentRequest(request, env)) ??
			new Response("Not found", { status: 404 })
		);
	},
} satisfies ExportedHandler<Env>;

各部分的作用如下:

  • Counter 扩展平台的 DurableObject。每个有名称的实例就是一个 Agent,并拥有自己的 SQLite 数据库。
  • State 会在该数据库中存储一个 JSON 值。get() 用于读取,set() 用于写入。每次状态更改后都会运行 onChanged,此处它会将新值发送给每个已连接的客户端。
  • WebSockets 用于接受 WebSocket 连接。启用 state 后,每个客户端连接时以及状态每次更改时都会收到当前值。callables 列出客户端可以调用的方法。
  • Lifecycle.install(this) 会接管对象的 fetch、alarm 和 WebSocket 处理程序,.use() 则用于添加各项功能。不要在类中自行定义 fetch() 或 alarm()。请改用 onRequest()、onStart() 和 onAlarm()。
  • onRequest() 处理未由任何功能处理的 HTTP 请求。POST 会递增计数器,GET 会返回状态。
  • routeAgentRequest() 会将 /agents/counter/:name 的请求发送到名为 :name 的 Counter 实例。

为 Env 接口生成类型:

npx wrangler types

在本地运行 Agent

  1. 启动开发服务器:

    npx wrangler dev
  2. 在另一个终端中,递增名为 demo 的计数器:

    curl -X POST http://localhost:8787/agents/counter/demo
    { "count": 1 }
  3. 读取另一个 counter 的状态。每个名称对应一个独立的 agent:

    curl http://localhost:8787/agents/counter/other
    { "count": 0 }

连接 client

来自 agents/client 的 AgentClient 会建立到 agent 的 WebSocket、保留其状态副本并调用其方法。在项目目录中创建 client.mjs:

client.mjsjs
import { AgentClient } from "agents/client";

const counter = new AgentClient({
	agent: "Counter",
	name: "demo",
	host: "localhost:8787",
	onStateUpdate: (state) => console.log("count is now", state.count),
});

await counter.ready;
await counter.stub.increment();
await counter.stub.increment();
counter.close();

在开发服务器运行时,运行该文件:

node client.mjs
count is now 1
count is now 2
count is now 3

第一行是 client 连接时收到的状态。其他两行是每次 increment() 后的广播。连接到 demo 的每个 client 都会收到相同更新,包括通过 HTTP 进行的更改。

在 React 应用程序中,使用来自 agents/react 的 useAgent hook。当 agent 状态更改时,agent.state 会更新:

import { useAgent } from "agents/react";
import type { CounterState } from "./index";

export function CounterView() {
	const agent = useAgent<CounterState>({ agent: "Counter", name: "demo" });

	return (
		<div>
			<p>{agent.state?.count ?? 0}</p>
			<button onClick={() => agent.stub.increment()}>+</button>
			<button onClick={() => agent.stub.reset()}>Reset</button>
		</div>
	);
}

client 也可以使用 agent.setState() 直接替换状态。要检查或拒绝这些更改,请将 validateStateChange 传递给 State,或使用 WebSockets 上的 readonly 选项将连接标记为只读。

工作原理

flowchart LR
    A["客户端"] <-->|WebSocket| B["计数器<br/>(Durable Object)"]
    C["curl"] -->|HTTP| B
    B --> D["SQLite<br/>(状态)"]
  • 每个名称对应一个 agent。 /agents/counter/demo 和 /agents/counter/other 是两个 Durable Object 实例,各自拥有独立数据库。
  • 持久状态。 State 会将每次更改写入 SQLite,因此计数可以在重启、部署和内存驱逐后继续存在。
  • 实时同步。 连接到同一 agent 的每个 client 都会收到每次更改。
  • 休眠。 WebSocket 连接使用 WebSocket Hibernation API,因此空闲 agent 会离开内存,同时 client 保持连接。收到消息时,Lifecycle 会在处理消息之前重新启动。

部署到 Cloudflare

npx wrangler deploy

你的 agent 现在运行在 Cloudflare 全球网络上。要调用它,请将 localhost:8787 替换为你的 workers.dev URL。已部署的 Worker 使用 HTTPS 和 WSS。

故障排除

向 /agents/... 发出的请求返回 404

  • 从 Worker 的主 module 导出 Durable Object 类。
  • 检查 binding 中的 class_name 和 exports 中的 key 是否与导出的类名匹配。
  • 默认 route 为 /agents/{binding}/{name},其中 binding 名称使用 kebab case。名为 Counter 的 binding 可通过 /agents/counter/... 访问,MyAgent 则通过 /agents/my-agent/... 访问。

对象无法确定自身名称

Lifecycle 需要 Durable Object 的名称。使用 getByName()、idFromName() 或 routeAgentRequest() 寻址 object。使用 newUniqueId() 或 idFromString() 创建的 object 没有名称。

onRequest() 或某项功能从未运行

从类中移除所有 fetch()、alarm()、webSocketMessage()、webSocketClose() 或 webSocketError() 方法。Lifecycle 只会安装类尚未定义的 handler。

后续步骤

这篇文档对您有帮助吗?