跳转到内容

部署到 Cloudflare Workers

Cloudflare Workers 是 Hashee 推荐的 serverless 部署目标——和 Hashee 自己的 后端用同一套基础设施,延迟、成本、可观测性都最好。本页讲完整的 Worker 部署, 含 Durable Objects 持久状态 + Queues 异步处理。

架构

Hashee Mobile / Desktop
Hashee API (盲管道路由)
│ HTTP POST {webhook_url} (1 MB body, 10 s deadline)
│ HMAC-SHA256 签名
你的 Worker (this guide)
├─ 立即 verify + 解密 → onMessage
├─ 短任务直接处理 + 200 OK
└─ 长任务 enqueue 到 Cloudflare Queues
Queue Consumer Worker
调 LLM / DB / 外部 API
restRequest POST /agents/:id/messages 回写 Hashee

项目脚手架

Terminal window
mkdir my-agent && cd my-agent
npm create cloudflare@latest . -- --type=hello-world --ts --no-deploy
npm install @hasheeai/agent-sdk-ts

wrangler.toml

name = "my-hashee-agent"
main = "src/worker.ts"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
[observability]
enabled = true
# Durable Object 用来存 per-conversation 状态
[[durable_objects.bindings]]
name = "CONVO"
class_name = "ConversationState"
[[migrations]]
tag = "v1"
new_classes = ["ConversationState"]
# Queue 用来异步处理长任务
[[queues.producers]]
queue = "agent-jobs"
binding = "JOBS"
[[queues.consumers]]
queue = "agent-jobs"
max_batch_size = 5
max_batch_timeout = 30

Worker 主入口

src/worker.ts
import { createWebhookDispatcher, restRequest } from "@hasheeai/agent-sdk-ts";
interface Env {
HASHEE_AGENT_ID: string;
HASHEE_AGENT_TOKEN: string;
HASHEE_WEBHOOK_SECRET: string;
HASHEE_X25519_PRIVATE_BASE64: string;
HASHEE_ED25519_PRIVATE_BASE64: string;
CONVO: DurableObjectNamespace;
JOBS: Queue<Job>;
}
interface Job {
conversation_id: string;
text: string;
sender_id: string;
}
export default {
// 接收 Hashee webhook
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (request.method !== "POST" || url.pathname !== "/hashee/webhook") {
return new Response("not found", { status: 404 });
}
const dispatcher = createWebhookDispatcher({
agentId: env.HASHEE_AGENT_ID,
token: env.HASHEE_AGENT_TOKEN,
secret: env.HASHEE_WEBHOOK_SECRET,
privateKeyBase64: env.HASHEE_X25519_PRIVATE_BASE64,
signingPrivateKeyBase64: env.HASHEE_ED25519_PRIVATE_BASE64,
onMessage: async (msg) => {
if (msg.payload?.type !== "text") return;
// 短问候直接 ack;长任务 enqueue
if (msg.payload.text.length < 50) {
ctx.waitUntil(
dispatcher.sendReply(msg.conversation_id, {
type: "text",
text: "收到,正在处理...",
}),
);
}
await env.JOBS.send({
conversation_id: msg.conversation_id,
text: msg.payload.text,
sender_id: msg.sender_id,
});
},
onEvent: async (event) => {
if (event.type === "relation.revoked") {
// 清 DO 里这个用户的状态
const id = env.CONVO.idFromName(event.payload.conversation_id);
await env.CONVO.get(id).fetch("/cleanup", { method: "POST" });
}
},
});
try {
await dispatcher.handle(
Object.fromEntries(request.headers),
await request.text(),
);
return new Response("ok", { status: 200 });
} catch (err: any) {
console.error("[hashee] dispatcher error:", err);
return new Response("error", { status: err?.statusCode ?? 500 });
}
},
// Queue 消费者
async queue(batch: MessageBatch<Job>, env: Env, ctx: ExecutionContext): Promise<void> {
for (const m of batch.messages) {
try {
await processJob(m.body, env, ctx);
m.ack();
} catch (e) {
console.error("[job] failed:", e);
m.retry(); // 走 Cloudflare Queues 重试
}
}
},
};
async function processJob(job: Job, env: Env, ctx: ExecutionContext): Promise<void> {
// 调 LLM (这里用 Cloudflare Workers AI 作为示例)
const reply = await fetch("https://api.cloudflare.com/client/v4/accounts/.../ai/run/@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
method: "POST",
headers: { Authorization: `Bearer ${env.CF_API_TOKEN}` },
body: JSON.stringify({ messages: [{ role: "user", content: job.text }] }),
}).then((r) => r.json());
await restRequest({
method: "POST",
baseUrl: "https://api.hashee.ai",
path: `/agents/${env.HASHEE_AGENT_ID}/messages`,
token: env.HASHEE_AGENT_TOKEN,
body: {
conversation_id: job.conversation_id,
payload: { type: "text", text: reply.result?.response ?? "(empty)" },
},
});
}
// Durable Object 存 per-conversation 状态
export class ConversationState {
constructor(private state: DurableObjectState, private env: Env) {}
async fetch(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/get") {
const data = await this.state.storage.get<unknown>("data");
return Response.json(data ?? null);
}
if (url.pathname === "/set" && req.method === "POST") {
const body = await req.json();
await this.state.storage.put("data", body);
return new Response("ok");
}
if (url.pathname === "/cleanup" && req.method === "POST") {
await this.state.storage.deleteAll();
return new Response("ok");
}
return new Response("not found", { status: 404 });
}
}

部署

Terminal window
# 设置 secret
wrangler secret put HASHEE_AGENT_ID
wrangler secret put HASHEE_AGENT_TOKEN
wrangler secret put HASHEE_WEBHOOK_SECRET
wrangler secret put HASHEE_X25519_PRIVATE_BASE64
wrangler secret put HASHEE_ED25519_PRIVATE_BASE64
wrangler secret put CF_API_TOKEN
# 创建 Queue
wrangler queues create agent-jobs
# 部署
wrangler deploy

部署成功后会显示:

Deployed my-hashee-agent triggers (X events)
https://my-hashee-agent.<subdomain>.workers.dev

把 Webhook URL 注册到 Agent

在 Hashee app 跟系统 Agent Hashee 说:

把 DemoBot 的连接模式改成 webhook
URL: https://my-hashee-agent.<subdomain>.workers.dev/hashee/webhook
secret: <你前面 wrangler secret 里设的 HASHEE_WEBHOOK_SECRET 值>

System Agent 会显示 Artifact 表单二次确认;提交后立即生效。

验证

在 Hashee app 给 DemoBot 发”你好”,几秒内应该收到回复。

Terminal window
wrangler tail my-hashee-agent
# POST /hashee/webhook 200 in 320ms
# [job] processing conv=01HZ...
# POST https://api.hashee.ai/agents/01906abc/.../messages 200 in 180ms

性能与限制

Workers 限Hashee 限
Webhook 同步响应30 s(CPU 时间)10 s
Webhook payload1 MB(超走 R2)
子请求数50 / request
Queue message size128 KB / msg
Queue retries默认 3 + DLQ
Durable Object req/s1000 / instance

关键:Webhook handler 必须 ≤ 10 s 完成(Hashee 这一侧的硬限)。 超过会被 Hashee 视为失败 + 触发重试 + 7 次后进入 unreachable。 所有长任务必须 enqueue 到 Queue 异步处理,handler 立即 200。

主动推送

// 在 cron trigger / 其他 Worker 里主动给 Hashee 用户推消息
import { restRequest } from "@hasheeai/agent-sdk-ts";
await restRequest({
method: "POST",
baseUrl: "https://api.hashee.ai",
path: `/agents/${env.HASHEE_AGENT_ID}/messages`,
token: env.HASHEE_AGENT_TOKEN,
body: {
conversation_id: targetConvId,
payload: { type: "text", text: "提醒:你的部署完成了" },
},
});

可以加到 cron trigger(wrangler.toml 里的 [triggers] crons = ["0 * * * *"])做定时通知。

持久状态:Durable Objects 还是 KV / D1?

用 DO用 KV用 D1
per-conversation 状态机(游戏 / 投票)配置 / 缓存(不需要强一致)关系数据 / 跨会话查询
强一致写入全球分布式读SQL 查询

Hashee 后端自己的 ConversationDO / DeliveryShardDO 也是这种用法。

私钥旋转

Terminal window
# 1. 在 Hashee app 让系统 Agent "重新生成 DemoBot Token + 重新生成密钥"
# 2. 拿到新 token + 新 base64 私钥
# 3. 重新 put secret
wrangler secret put HASHEE_AGENT_TOKEN
wrangler secret put HASHEE_X25519_PRIVATE_BASE64
wrangler secret put HASHEE_ED25519_PRIVATE_BASE64
# 4. 自动生效(下次 webhook 调用拿新 secret)

故障排查

症状排查
wrangler tail 看不到 webhook 调用检查 Hashee Agent 详情页”连接状态”是否 webhook + URL 是否正确
401 invalid signaturesecret 错;重 put 一次确保和 Hashee 后端一致
409 AGENT_KEYS_MISMATCH私钥与后端公钥不匹配;重做 keys/register
Worker 超时长任务必须 enqueue;handler 立即 200
Queue 累积看 consumer worker 是否能跟上;增 max_batch_size

演示视频

视频文字版逐节描述
  • 00:00 – 00:15 — 终端 wrangler deploy → 输出 deployment URL。
  • 00:15 – 00:30 — 在 Hashee app 跟系统 Agent 说”把 DemoBot 改成 webhook, url=…secret=…”;提交确认。
  • 00:30 – 00:45 — 给 DemoBot 发”你好”,几秒后收到回复”收到,正在处理…”。 紧接着收到 LLM 完整回复”你好!需要帮忙吗?”。
  • 00:45 – 01:00wrangler tail 显示 POST /hashee/webhook 200 in 290ms
    • [job] processing + POST .../messages 200 in 180ms
  • 01:00 – 01:15 — 演示长任务:用户问”详细介绍 React 18”,Worker handler 立即 200 ack(看到第一个 “收到” 回复秒回),LLM 30 秒后完成生成, 完整回复才显示。Queue 异步处理避免了 webhook timeout。

下一步