开发者指南

为 Claude Agent SDK 智能体添加操作前检查

如果必须在工具运行前进行检查,Claude Agent SDK 工具调用需要 PreToolUse 钩子。

检查的作用

检查返回 permit、 deny或 escalate ,并附上决定结果的条款和签名回执。检查仅提供建议,真正拒绝执行的是你的 PreToolUse 钩子。托管检查在内存中评估请求,不保留请求内容。

代码

这是经过审计的示例,与以下位置发布的内容完全一致: public/examples/frameworks/.

/** Claude Agent SDK PreToolUse hook that checks HANRIA before execution. */

import { query, type HookCallback, type HookCallbackMatcher, type Options } from "@anthropic-ai/claude-agent-sdk";
import { pathToFileURL } from "node:url";

const CHECK_URL = "https://check.hanria.ai/v1/check";
const MANDATE = {
  schema_version: "0.2-draft",
  mandate_id: "review-tool-calls",
  purpose: "Permit file reads under the example directory.",
  default: "deny",
  clauses: [{
    id: "permit-example-read",
    effect: "permit",
    match: { kind: ["file"], verb: ["read"], target_prefix: ["/tmp/example/"] },
  }],
};

type FetchLike = typeof globalThis.fetch;

export async function checkAction(
  action: Record<string, unknown>,
  checkUrl = CHECK_URL,
  fetchImpl: FetchLike = globalThis.fetch,
): Promise<Record<string, unknown>> {
  try {
    const response = await fetchImpl(checkUrl, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ mandate: MANDATE, action }),
      signal: AbortSignal.timeout(10000),
    });
    if (!response.ok) return { outcome: "error", reason: `HANRIA returned HTTP ${response.status}` };
    const decision = await response.json() as Record<string, unknown>;
    if (!["permit", "deny", "escalate", "error"].includes(String(decision.outcome))) {
      return { outcome: "error", reason: "HANRIA returned an invalid decision" };
    }
    return decision;
  } catch (error) {
    return { outcome: "error", reason: `HANRIA check failed: ${String(error)}` };
  }
}

export function createHanriaHook({
  checkUrl = CHECK_URL,
  fetchImpl = globalThis.fetch,
}: { checkUrl?: string; fetchImpl?: FetchLike } = {}): HookCallback {
  return async (input) => {
    if (input.hook_event_name !== "PreToolUse") {
      return { continue: false, stopReason: "Expected a PreToolUse event" };
    }
    const toolInput = input.tool_input as Record<string, unknown>;
    const action = {
      schema_version: "0.1-draft",
      requested_by: { agent: "claude-agent-sdk-example" },
      operation: {
        kind: input.tool_name === "Read" ? "file" : "tool",
        verb: input.tool_name === "Read" ? "read" : input.tool_name,
        target: String(toolInput.file_path ?? toolInput.path ?? input.tool_name),
      },
      justification: `Run ${input.tool_name} through the Claude Agent SDK.`,
    };
    const decision = await checkAction(action, checkUrl, fetchImpl);
    if (decision.outcome === "permit") return {};
    return {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "deny",
        permissionDecisionReason: `HANRIA returned ${String(decision.outcome ?? "error")}: ${String(decision.reason ?? "no reason returned")}`,
      },
    };
  };
}

export function createHookOptions(settings: { checkUrl?: string; fetchImpl?: FetchLike } = {}): Options {
  const matcher: HookCallbackMatcher = { hooks: [createHanriaHook(settings)] };
  return { hooks: { PreToolUse: [matcher] } };
}

export function constructQuery(prompt: string, options = createHookOptions()) {
  return query({ prompt, options });
}

if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
  const options = createHookOptions();
  console.log(`Constructed ${options.hooks?.PreToolUse?.length ?? 0} HANRIA PreToolUse hook matcher.`);
}

运行

保存 claude_agent_sdk.ts,然后在保存该文件的文件夹中运行以下命令。

npm install --no-save --save-exact @anthropic-ai/[email protected] [email protected]
npx tsx claude_agent_sdk.ts

该命令构造一个 PreToolUse 钩子匹配器,但不会启动查询。在你的应用程序中,将 createHookOptions() 传给 SDK 查询,并把每个工具输入映射到授权书所检查的操作字段。

处理每一种结果

超时、传输故障、非成功 HTTP 响应、格式错误的响应或未知结果都会变为 error。除准确的 permit 以外的每个结果都会被钩子拒绝。

参考资料

试用检查 · 验证签名回执 · OpenAPI 文档 · 面向智能体的摘要