Агенты

Как создать AI-агента с помощью Claude Agent SDK (2026)

Пошаговое руководство: собираем агента для код-ревью на Claude Agent SDK. Query loop, инструменты, субагенты и структурированный вывод на TypeScript.

Илья Новиков
Илья НовиковГлавный объяснитель
16 января 2026 г.10 мин чтения
Поделиться:
Схема, показывающая файлы кода, проходящие через AI-агента, который выдаёт структурированный отчёт с индикаторами критичности

ИНФО

Сложность Средняя
Время 45-60 минут
Что нужно Node.js 18+, API-ключ Anthropic, базовое знание TypeScript
Инструменты Claude Code CLI, @anthropic-ai/claude-agent-sdk

Чему научитесь:

  • Настроить SDK и запустить первый запрос
  • Собрать агента для код-ревью, который читает файлы и возвращает структурированные находки
  • Создать субагентов для специализированных задач вроде проверки безопасности
  • Работать с разрешениями, сессиями и кастомными инструментами через MCP

Agent SDK — это инфраструктура, на которой работает Claude Code, упакованная в библиотеку. Вы получаете готовый цикл агента, встроенные инструменты для работы с файловой системой и управление контекстом. В этом руководстве мы соберём агента для код-ревью с нуля. На выходе получится штука, которая сканирует кодовую базу, находит проблемы и возвращает структурированный отчёт.

Начинаем

Сначала ставим Claude Code CLI. Agent SDK использует его как рантайм:

npm install -g @anthropic-ai/claude-code

Запустите claude в терминале и пройдите аутентификацию. Потом настройте проект:

mkdir code-review-agent && cd code-review-agent
npm init -y
npm install @anthropic-ai/claude-agent-sdk
npm install -D typescript @types/node tsx

Задайте API-ключ:

export ANTHROPIC_API_KEY=your-api-key

SDK vs голый API

Если вы уже делали агентов на сыром Messages API, то знаете цикл: вызвать модель, проверить, хочет ли она инструмент, выполнить инструмент, скормить результат обратно, повторить. SDK берёт это на себя.

// Голый API: вы управляете циклом
let response = await client.messages.create({...});
while (response.stop_reason === "tool_use") {
  const result = yourToolExecutor(response.tool_use);
  response = await client.messages.create({ tool_result: result, ... });
}

// SDK: Claude управляет сам
for await (const message of query({ prompt: "Fix the bug in auth.py" })) {
  console.log(message);
}

SDK также даёт инструменты из коробки: Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch. Ничего из этого реализовывать самим не нужно.

Первый агент

Создаём agent.ts:

import { query } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  for await (const message of query({
    prompt: "What files are in this directory?",
    options: {
      model: "opus",
      allowedTools: ["Glob", "Read"],
      maxTurns: 250
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if ("text" in block) {
          console.log(block.text);
        }
      }
    }
    
    if (message.type === "result") {
      console.log("\nDone:", message.subtype);
    }
  }
}

main();

Запускаем через npx tsx agent.ts. Claude использует Glob, чтобы получить список файлов, и сообщает о найденном.

Типы сообщений

Функция query() возвращает асинхронный генератор. Основные типы сообщений:

for await (const message of query({ prompt: "..." })) {
  switch (message.type) {
    case "system":
      // Инициализация сессии
      if (message.subtype === "init") {
        console.log("Session ID:", message.session_id);
      }
      break;
      
    case "assistant":
      // Ответы Claude и вызовы инструментов
      for (const block of message.message.content) {
        if ("text" in block) {
          console.log("Claude:", block.text);
        } else if ("name" in block) {
          console.log("Tool call:", block.name);
        }
      }
      break;
      
    case "result":
      // Финальный результат
      console.log("Status:", message.subtype);
      console.log("Cost:", message.total_cost_usd);
      break;
  }
}

Собираем агента для код-ревью

Создаём review-agent.ts:

import { query } from "@anthropic-ai/claude-agent-sdk";

async function reviewCode(directory: string) {
  console.log(`\n🔍 Starting code review for: ${directory}\n`);
  
  for await (const message of query({
    prompt: `Review the code in ${directory} for:
1. Bugs and potential crashes
2. Security vulnerabilities  
3. Performance issues
4. Code quality improvements

Be specific about file names and line numbers.`,
    options: {
      model: "opus",
      allowedTools: ["Read", "Glob", "Grep"],
      permissionMode: "bypassPermissions",
      maxTurns: 250
    }
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if ("text" in block) {
          console.log(block.text);
        } else if ("name" in block) {
          console.log(`\n📁 Using ${block.name}...`);
        }
      }
    }
    
    if (message.type === "result") {
      if (message.subtype === "success") {
        console.log(`\n✅ Review complete! Cost: $${message.total_cost_usd.toFixed(4)}`);
      } else {
        console.log(`\n❌ Review failed: ${message.subtype}`);
      }
    }
  }
}

reviewCode(".");

Настройка permissionMode: "bypassPermissions" автоматически одобряет операции чтения. Для теста создайте файл с намеренными багами:

// example.ts
function processUsers(users: any) {
  for (let i = 0; i <= users.length; i++) { // Off-by-one
    console.log(users[i].name.toUpperCase()); // No null check
  }
}

function connectToDb(password: string) {
  const connectionString = `postgres://admin:${password}@localhost/db`;
  console.log("Connecting with:", connectionString); // Logging credentials
}

Запускаем npx tsx review-agent.ts. Claude найдёт баги, проблему с логированием пароля и предложит исправления.

Структурированный вывод

Для программного использования нужен JSON. SDK поддерживает JSON Schema:

const reviewSchema = {
  type: "object",
  properties: {
    issues: {
      type: "array",
      items: {
        type: "object",
        properties: {
          severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
          category: { type: "string", enum: ["bug", "security", "performance", "style"] },
          file: { type: "string" },
          line: { type: "number" },
          description: { type: "string" },
          suggestion: { type: "string" }
        },
        required: ["severity", "category", "file", "description"]
      }
    },
    summary: { type: "string" },
    overallScore: { type: "number" }
  },
  required: ["issues", "summary", "overallScore"]
};

async function reviewCodeStructured(directory: string) {
  for await (const message of query({
    prompt: `Review the code in ${directory}. Identify all issues.`,
    options: {
      model: "opus",
      allowedTools: ["Read", "Glob", "Grep"],
      permissionMode: "bypassPermissions",
      maxTurns: 250,
      outputFormat: {
        type: "json_schema",
        schema: reviewSchema
      }
    }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      const review = message.structured_output;
      
      console.log(`\nScore: ${review.overallScore}/100`);
      console.log(`Summary: ${review.summary}\n`);
      
      for (const issue of review.issues) {
        const icon = issue.severity === "critical" ? "🔴" :
                     issue.severity === "high" ? "🟠" :
                     issue.severity === "medium" ? "🟡" : "🟢";
        console.log(`${icon} [${issue.category}] ${issue.file}${issue.line ? `:${issue.line}` : ""}`);
        console.log(`   ${issue.description}`);
      }
    }
  }
}

Управление разрешениями

Доступны три режима:

options: {
  permissionMode: "default",        // Запрашивает одобрение
  permissionMode: "acceptEdits",    // Автоматически одобряет редактирование файлов
  permissionMode: "bypassPermissions" // Без запросов
}

Для тонкой настройки есть canUseTool:

options: {
  canUseTool: async (toolName, input) => {
    // Разрешаем всё чтение
    if (["Read", "Glob", "Grep"].includes(toolName)) {
      return { behavior: "allow", updatedInput: input };
    }
    
    // Блокируем запись в .env
    if (toolName === "Write" && input.file_path?.includes(".env")) {
      return { behavior: "deny", message: "Cannot modify .env files" };
    }
    
    return { behavior: "allow", updatedInput: input };
  }
}

Субагенты

Для комплексных ревью можно делегировать работу специализированным субагентам:

import { query, AgentDefinition } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: `Perform a comprehensive code review of ${directory}. 
Use security-reviewer for vulnerability detection.`,
  options: {
    model: "opus",
    allowedTools: ["Read", "Glob", "Grep", "Task"],
    permissionMode: "bypassPermissions",
    maxTurns: 250,
    agents: {
      "security-reviewer": {
        description: "Security specialist for vulnerability detection",
        prompt: `You are a security expert. Focus on:
- SQL injection, XSS, CSRF vulnerabilities
- Exposed credentials and secrets
- Insecure data handling`,
        tools: ["Read", "Grep", "Glob"],
        model: "sonnet"
      } as AgentDefinition
    }
  }
})) {
  // Главный агент может делегировать через инструмент Task
  // Субагенты работают со своей моделью и ограничениями инструментов
}

Инструмент Task позволяет делегировать. Главный агент решает, когда передать работу. Для субагентов можно использовать более дешёвые модели (Sonnet, Haiku).

Управление сессиями

Для многооборотных разговоров сохраняйте session ID и продолжайте:

let sessionId: string | undefined;

// Первый запрос
for await (const message of query({
  prompt: "Review this codebase and identify the top 3 issues",
  options: { model: "opus", allowedTools: ["Read", "Glob", "Grep"], maxTurns: 250 }
})) {
  if (message.type === "system" && message.subtype === "init") {
    sessionId = message.session_id;
  }
}

// Продолжение с сохранённым контекстом
if (sessionId) {
  for await (const message of query({
    prompt: "Now show me how to fix the most critical issue",
    options: {
      resume: sessionId,
      allowedTools: ["Read", "Glob", "Grep"],
      maxTurns: 250
    }
  })) {
    // Claude помнит предыдущие находки
  }
}

Хуки

Хуки перехватывают вызовы инструментов для логирования, валидации или блокировки:

import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";

const auditLogger: HookCallback = async (input, toolUseId, { signal }) => {
  if (input.hook_event_name === "PreToolUse") {
    const preInput = input as PreToolUseHookInput;
    console.log(`[AUDIT] ${new Date().toISOString()} - ${preInput.tool_name}`);
  }
  return {};
};

const blockDangerous: HookCallback = async (input, toolUseId, { signal }) => {
  if (input.hook_event_name === "PreToolUse") {
    const preInput = input as PreToolUseHookInput;
    if (preInput.tool_name === "Bash") {
      const command = (preInput.tool_input as any).command || "";
      if (command.includes("rm -rf") || command.includes("sudo")) {
        return {
          hookSpecificOutput: {
            hookEventName: "PreToolUse",
            permissionDecision: "deny",
            permissionDecisionReason: "Dangerous command blocked"
          }
        };
      }
    }
  }
  return {};
};

for await (const message of query({
  prompt: "Clean up temporary files",
  options: {
    model: "opus",
    allowedTools: ["Bash", "Glob"],
    maxTurns: 50,
    hooks: {
      PreToolUse: [
        { hooks: [auditLogger] },
        { matcher: "Bash", hooks: [blockDangerous] }
      ]
    }
  }
})) {
  // ...
}

Поле matcher принимает регулярное выражение. "Bash" срабатывает только на Bash; "Bash|Write|Edit" — на любой из них.

Кастомные инструменты через MCP

Встроенные инструменты покрывают файловую систему и веб-операции. Для всего остального используйте Model Context Protocol:

import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const customServer = createSdkMcpServer({
  name: "code-metrics",
  version: "1.0.0",
  tools: [
    tool(
      "analyze_complexity",
      "Calculate cyclomatic complexity for a file",
      { filePath: z.string().describe("Path to the file to analyze") },
      async (args) => {
        // Реальная имплементация считала бы настоящую сложность
        const complexity = Math.floor(Math.random() * 20) + 1;
        return {
          content: [{
            type: "text",
            text: `Cyclomatic complexity for ${args.filePath}: ${complexity}`
          }]
        };
      }
    )
  ]
});

for await (const message of query({
  prompt: `Analyze the complexity of main.ts`,
  options: {
    model: "opus",
    mcpServers: { "code-metrics": customServer },
    allowedTools: ["Read", "mcp__code-metrics__analyze_complexity"],
    maxTurns: 50
  }
})) {
  // Claude теперь может вызывать ваш кастомный инструмент
}

Инструменты MCP следуют шаблону именования mcp__<имя-сервера>__<имя-инструмента>. Схема Zod определяет входные данные; обработчик запускается, когда Claude вызывает инструмент.

Отслеживание расходов

if (message.type === "result" && message.subtype === "success") {
  console.log("Total cost:", message.total_cost_usd);
  
  // Разбивка по моделям (полезно с субагентами)
  for (const [model, usage] of Object.entries(message.modelUsage)) {
    console.log(`${model}: $${usage.costUSD.toFixed(4)}`);
  }
}

Решение проблем

"Cannot find module '@anthropic-ai/claude-agent-sdk'" Выполните npm install @anthropic-ai/claude-agent-sdk в директории проекта. Пакет должен быть локальным, не только глобальным.

Ошибки аутентификации при запуске запросов Сначала запустите claude в терминале и пройдите авторизацию. SDK использует аутентификацию Claude Code.

Агент зависает или выдаёт таймаут Проверьте настройку maxTurns. Сложные задачи могут требовать 100+ итераций. Также убедитесь, что есть доступ к сети, если инструменты требуют внешних ресурсов.

Структурированный вывод возвращает null Убедитесь, что JSON-схема валидна. Попробуйте сначала более простую схему. Массив required должен содержать только существующие свойства.

Субагент не вызывается Нужен Task в массиве allowedTools. Без него главный агент не может делегировать.

Что дальше

Полноценный продакшн-агент показан в коде выше. Дальше можно изучить чекпоинтинг файлов для отслеживания изменений, систему Skills для упаковки переиспользуемых возможностей или паттерны деплоя для интеграции в CI/CD.


СОВЕТЫ

Настройка maxTurns ограничивает количество итераций агента. Для код-ревью больших кодовых баз 250 — разумное значение. Для быстрых проверок одного файла хватит 50.

Для субагентов с простым анализом используйте модель haiku. Она быстрее и дешевле. Оставьте opus для главного оркестратора и сложных рассуждений.

При отладке добавьте хук, который логирует каждый вызов инструмента. Это быстрее, чем продираться через консольный вывод, пытаясь понять, что Claude сделал.

Если упираетесь в rate limits, SDK не делает автоматических повторов. Оберните запрос в try-catch и реализуйте exponential backoff.


ЧаВо

Можно использовать Python вместо TypeScript? Да, есть Python SDK с теми же возможностями. Ссылка на документацию ниже.

Как ограничить, к каким файлам агент может обращаться? Используйте колбэк canUseTool. Проверяйте file_path или pattern во входных данных и возвращайте { behavior: "deny" } для закрытых путей.

Агент помнит предыдущие разговоры? Только в рамках сессии. Используйте опцию resume с session ID, чтобы продолжить разговор. Постоянной памяти между сессиями нет.

Сколько стоит типичный код-ревью? Зависит от размера кодовой базы. Небольшой проект (10-20 файлов) на Opus обычно обходится в $0.10-0.30. Большие базы или множественные вызовы субагентов стоят дороже.


РЕСУРСЫ

Илья Новиков

Илья Новиков

Главный объяснитель

Илья тот самый человек, которому друзья пишут, когда ломается Wi-Fi, код не компилируется или инструкции к мебели выглядят как загадка. Теперь он направляет этот опыт в практичные гайды, которые помогают тысячам читателей решать проблемы спокойно и без паники.

Похожие статьи

Будьте впереди в мире ИИ

Получайте последние новости, обзоры и скидки ИИ прямо на почту. Присоединяйтесь к 100 000+ энтузиастов ИИ.

Подписываясь, вы соглашаетесь с нашей Политикой конфиденциальности. Отписаться можно в любое время.