关于 integrate-arcjet-guard-cloudflare-think
此技能指导将 Arcjet Guard 集成到 Cloudflare Think,以控制服务端工具调用并筛查入站消息。面向使用指定 Think 版本范围的开发者,说明拒绝行为、调用者身份和关联标识的处理方式。
主要功能
- 01通过 `beforeToolCall` 控制服务端工具
- 02支持阻止或替代式拒绝决策
- 03在对话轮次开始前筛查入站消息
- 04传递已验证的调用者和策略输入
- 05读取由调用方提供的关联标识
适用场景
- 01拒绝或替代有风险的工具调用
- 02在 Agent 轮次前筛查用户消息
- 03使用调用者和输入应用远程规则
- 04将 Guard 决策关联到会话上下文
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ arcjet/ skills --skill integrate-arcjet-guard-cloudflare-think安装后在 Claude Code 中输入/integrate-arcjet-guard-cloudflare-think确认已加载。
详细介绍
在 Cloudflare Think 中集成 Arcjet Guard
@arcjet/guard/cloudflare-think/v0 会包装 Agent 现有的 Arcjet
客户端。它不会自行与 Arcjet API 通信。共享的 Guard
基础内容(客户端、规则、标签、决策、捕获、注册)
位于
../arcjet/references/guards_javascript.md。
对于 Think 专属内容以外的所有内容,请加载该参考文档。
仅支持官方 @cloudflare/think >=0.3.0 <1——这是首个
beforeToolCall 返回可用 ToolCallDecision(allow / block / substitute),并包装每个服务端工具的 execute、使钩子先运行的版本。Think 发布
1.x 之前不会有 /v1。这不是 Vercel AI SDK(ai /
@arcjet/guard/vercel-ai/v7)。Think 在内部管理 streamText;不要再用 AI SDK mix-in 包装其工具。这不是 TanStack Start HTTP protect()。也不是 Cloudflare Workers HTTP protect()(@arcjet/node / 基于请求)。
导出项:guardHooks、cloudflareThinkContext。没有
guardTool,也没有 guardThink mixin。跳过工具调用要通过
beforeToolCall 的返回值实现,而不是从 execute 抛出异常。没有
guardInbound,也没有 guardApproval。没有未带版本号的
@arcjet/guard/cloudflare-think 别名。
两个接口,一个决策规则:
- 工具调用 →
guardHooks。返回一个{ beforeToolCall }对象,Think子类会将其委托给该对象。交付结果是 ThinkToolCallDecision:返回 substitute({ action: "substitute", output: ArcjetDenialResult }),让模型看到载荷;或返回 block({ action: "block", reason }),让模型看到原因字符串。返回void/{ action: "allow" }则会执行工具。不要抛出异常——抛出的是原始异常 /onChatError,不是拒绝结果。 - 关联 →
cloudflareThinkContext读取由调用方提供的 ID,不会生成 ID,也不会读取 Durable Object ID、toolCallId、requestId或traceId。
文档:https://docs.arcjet.com/guards/cloudflare-think/
不要另设第二个 slug,也不要覆盖其他任何
/guards/... 页面。示例(main 树中的链接返回 404;目前没有开放的示例 PR,因此在
examples/cloudflare-think-agent 合并到 main 之前,请引用此分支):
examples/cloudflare-think-agent
(没有验证 fixture)。不要另设第二个示例名称。
闸门是 beforeToolCall 的 block / substitute
Think 会包装每个服务端工具的 execute,并先调用
beforeToolCall(先运行子类钩子,再运行扩展)。返回
block 或 substitute 会跳过 execute。必须失败时关闭:出错时始终返回
其中之一,不要返回 void(这会执行工具),也不要抛出异常。默认 DENY 为 substitute,并附带
ArcjetDenialResult,让模型看到 { arcjetDenied: true, … }。
onDeny: "block" 则使用 { action: "block", reason },并丢弃结构化字段。onDeny: "block" 仅适用于实际
DENY;不可用时仍会通过 substitute 失败关闭。核心 guard() 仍会失败放行(hasFailedOpen())。
客户端工具(没有服务端 execute)不会进入此钩子。Think 自动合并的工作区工具只要具有 execute,也会进入此钩子。
needsApproval 不是策略闸门
AI SDK needsApproval / 确认式工具 / 人工恢复属于人机协作流程。人工确认后,Guard 仍会在工具调用上运行。这与 TanStack needsApproval、Mastra
requireApproval、Claude canUseTool、LangGraph interrupt()、
OpenAI Agents needsApproval、Genkit interrupt() 和 Google ADK
requireConfirmation 的情况一样。没有 guardApproval。
在 chat() / submitMessages() 之前筛查入站请求
没有 guardInbound。beforeTurn / beforeStep 用于调整模型调用;它们不是此策略门控。在轮次开始前,在应用程序中调用 arcjet.guard()(或在 beforeTurn 开始时调用并根据决策采取措施)。核心 guard() 会默认放行失败的情况:ALLOW 并不能证明规则已运行。如果此调用点必须采用失败时拒绝策略,请根据 decision.hasFailedOpen() 进行门控;guardHooks 已默认采用此策略。
actor / inputs——省略后,远程规则永远不会触发
只有当此次调用传入 actor 或类型化的 inputs 时,声明了这些值的远程策略才会评估它们。在 guardHooks(以及入站 arcjet.guard())中传入 actor 和 inputs。从 @arcjet/guard 导入 policyInput,不要从适配器路径导入。检查已安装的类型——不要传入辅助函数未声明的字段。
- 省略
actor/inputs后,要求这些值的远程策略永远不会触发。守卫调用仍会执行;只是远程规则没有可读取的内容。 - 解析器抛出异常时会失败关闭,这是默认的
onGuardError: "deny"行为(不可用时的替代方案——绝不能是void)。 actor是经过身份验证的调用方,绝不是由模型生成的工具参数。Think 信封中的input由模型控制。应从经过身份验证的服务器端上下文(已签名的会话、已验证的令牌,或你已经信任的 Durable Object 状态)派生actor,而不是从ctx.input派生。
rules / action / metadata 可以访问 { toolName, input }。actor / inputs 解析器还会将 Think 的 beforeToolCall 上下文作为第二个参数接收。
先询问用户的问题
只询问无法从代码中推断的信息;同时建议默认值。
- 哪些工具有风险(会产生外部副作用、不可逆、花费金钱或发送消息)?这些工具由
guardHooks门控。不带execute的客户端工具不在范围内。 - 有哪些限制?(例如:“每位用户每分钟查询 10 次” →
tokenBucket。) - 元数据中的用户是谁——不透明的用户/租户 ID(绝不能是 PII)?默认值:无。同一个经过身份验证的 ID 就是
actor。将你已有的会话/会话 ID 传入guardHooks({ sessionId })。该 ID 是关联 ID,不是用户 ID。不要使用 Durable Object ID 或toolCallId。 - Arcjet 停机是否不可接受?所有辅助函数的默认值都是
onGuardError: "deny"。务必明确询问轮次开始前的入站筛查:在那里失败关闭意味着 Agent 不会回答,因此在那个调用点选择"allow"是常见且合理的做法。
读者常犯的错误
- 这不是 Vercel AI SDK。 不要导入
@arcjet/guard/vercel-ai/v7,也不要用该命名空间中的guardTool包装 Think 工具。Think 已经管理streamText。 - 没有
guardInbound。 在chat()/submitMessages()之前用核心guard()进行筛查。 needsApproval是 HITL,不是策略。- 失败关闭 = 出错时始终返回
block或substitute。void会执行工具。抛出异常不等于拒绝。即使onDeny: "block",不可用时也会通过 substitute 失败关闭。 - 默认的 DENY 是 substitute(结构化的
ArcjetDenialResult)。block是返回原因字符串的路径,并会丢弃字段。它仅适用于真正的 DENY。 guardHooks不是混入。 将beforeToolCall委托给hooks.beforeToolCall(ctx)。不存在guardThink(Think, …)。- 关联 ID 只读取,不生成。 绝不能使用 Durable Object ID、
toolCallId、requestId或traceId。 - 导入路径带有版本号。
@arcjet/guard/cloudflare-think/v0可以解析;@arcjet/guard/cloudflare-think不可以。 - 基于经过身份验证的调用方设置速率限制键,不要基于模型提供的订单 ID。
actor也一样。 - 省略
actor/inputs后,远程规则永远不会触发。 解析器抛出异常时会失败关闭。请从@arcjet/guard导入policyInput。 - 不要用原始
guard()手动包装每个 Think 工具。
步骤 1:安装并查找 guard 客户端
安装已发布的 @arcjet/guard 和对等依赖 @cloudflare/think。
使用项目当前已有的包管理器——不要固定 git SHA:
npm install @arcjet/guard @cloudflare/think
pnpm add @arcjet/guard @cloudflare/think
对等依赖版本范围为 @cloudflare/think >=0.3.0 <1。带版本号的路径
@arcjet/guard/cloudflare-think/v0 可以解析;不带版本号的
@arcjet/guard/cloudflare-think 路径则不行。如果 Agent 尚无
guard 客户端,请在模块作用域中仅启动一次:
import { launchArcjet } from "@arcjet/guard";
export const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
步骤 2:拦截工具调用——委托给 beforeToolCall
将当前对话经过身份验证的调用方传入
guardHooks。Durable Object 通常是每个对话对应一个——但仍
不要将 this.ctx.id / this.name 用作 actor 或关联标识。
import { Think } from "@cloudflare/think";
import { launchArcjet, policyInput, tokenBucket } from "@arcjet/guard";
import { guardHooks } from "@arcjet/guard/cloudflare-think/v0";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const lookupLimit = tokenBucket({
bucket: "lookups",
refillRate: 10,
intervalSeconds: 60,
maxTokens: 10,
});
// From auth middleware or Durable Object state you already trust.
// Not this.ctx.id / this.name, and not a model-produced argument.
const userId = authenticatedUserId;
const conversationId = authenticatedConversationId;
const hooks = guardHooks(arcjet, {
action: ({ toolName }) => `${toolName}.invoked`,
// Authenticated caller — never a model-produced order id / ctx.input.
actor: userId,
inputs: ({ toolName }) => ({
tool: policyInput.server.string(toolName),
}),
// Keyed on the authenticated caller, not the model-supplied order id.
rules: () => [lookupLimit({ key: userId, requested: 1 })],
sessionId: conversationId,
onGuardError: "deny",
// default DENY is substitute (ArcjetDenialResult). onDeny: "block"
// uses { action: "block", reason } on real DENY only. Unavailable
// still substitutes.
});
export class SupportAgent extends Think<Env> {
// needsApproval on a tool is HITL — not this policy gate
override beforeToolCall = hooks.beforeToolCall;
}
如果子类已经实现了 beforeToolCall,请调用
hooks.beforeToolCall(ctx) 并返回其 ToolCallDecision。不要
忽略 DENY,也不要再用
@arcjet/guard/vercel-ai/v7 包装。
省略 rules 即表示不提交任何规则——guard 调用仍会执行。如果策略允许工具运行,
ALLOW 会记录 outcome: "success",而不是等到 execute 完成后才记录。
步骤 3:在轮次开始前筛查入站请求
import { detectPromptInjection, policyInput } from "@arcjet/guard";
import { cloudflareThinkContext } from "@arcjet/guard/cloudflare-think/v0";
const inbound = detectPromptInjection();
const decision = await arcjet.guard({
label: "message.received",
actor: userId,
inputs: { content: policyInput.server.string(userText) },
rules: [inbound(userText)],
...cloudflareThinkContext({ context: { sessionId: conversationId } }),
});
if (decision.conclusion === "DENY") {
throw new Error("message blocked");
}
if (decision.hasFailedOpen()) {
throw new Error("inbound guard unavailable");
}
// Then chat() / submitMessages() / the WebSocket turn.
没有 guardInbound。guard() 会故障放行——务必检查
hasFailedOpen()。
步骤 4:关联
cloudflareThinkContext 会读取调用方持有的 ID。优先顺序:
调用方持有的包装对象中的 correlationId,然后是 sessionId,再然后是 conversationId(cloudflareThinkContext({ context: appContext })),
接着是裸应用对象上的副本,然后是 guardHooks({ sessionId }) /
init.correlationId。它不会生成 ID。它不会读取 Durable
Object ID / this.ctx.id / this.name。它不会读取 Think 或 AI SDK 生成的
toolCallId、requestId、traceId 或流 ID。具有
toolCallId 和 toolName 的 beforeToolCall 上下文是
Think 的封装对象——该对象顶层的 sessionId 会被忽略。
不要为每个轮次自行生成关联 ID。如果没有剩下任何有效 ID,
此次调用就不关联,而不是与生成的 ID 关联。
验证集成
npm run typecheck(或项目的类型检查)通过。- 测试入站 PI(轮次开始前,包括
hasFailedOpen())、替代式拒绝(模型会看到ArcjetDenialResult)、阻止式拒绝(原因字符串;仅限真正的 DENY)、 服务不可用 +onDeny: "block"时仍采用替代式拒绝、无返回值的 execute、不抛出异常、绝不生成 ID、省略actor/inputs(远程规则不执行)、解析器抛出异常(故障关闭并采用替代式拒绝),以及故障关闭(guard 不可达时 → 替代式拒绝,绝不无返回值)。确认needsApproval从未被 当作拦截机制,并且工具没有再用@arcjet/guard/vercel-ai/v7包装。 - 在 Arcjet Console / CLI 中确认,决策共用调用方持有的
session / conversation ID——而不是 Durable Object ID 或
toolCallId——且actor是经过身份验证的调用方。 - 使用真实
ARCJET_KEY进行的手动 E2E 测试,在实际运行之前仍属待验证。
示例:
examples/cloudflare-think-agent
(无 verify fixture;main 中尚无此目录树)。不要
另行编造示例名称。不要在此 skills 仓库中添加示例。