開発者ガイド
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 を保存し、そのフォルダーで次の 2 つのコマンドを実行します。
npm install --no-save --save-exact @anthropic-ai/[email protected] [email protected]
npx tsx claude_agent_sdk.ts
このコマンドは 1 つの PreToolUse フックマッチャーを構築しますが、クエリは開始しません。アプリケーション内の SDK クエリに createHookOptions() を渡し、各ツール入力をマンデートがチェックする行為フィールドに対応付けます。
すべての結果を処理する
- permit: フックは許可判断を返さないため、SDK は通常の許可フローを続けます。
- deny: フックはツール実行前に拒否の許可判断を返します。
- escalate: フックは拒否の許可判断を返します。このフックの外で運用者に判断を求めてください。
- error: フックは拒否の許可判断を返します。deny として扱い、理由を報告し、無分別に再試行しないでください。
タイムアウト、転送障害、成功以外の HTTP 応答、不正な形式の応答、または未知の結果は error になります。正確な permit 以外のすべての結果はフックによって拒否されます。