Перейти к содержимому

Claude Code

Обновление образов

Готовые образы агентов обновляются каждое утро и содержат актуальную версию CLI. Чтобы использовать фиксированную версию образа, закрепите версию шаблона.

Claude Code — агент Anthropic, который читает и изменяет файлы, выполняет команды и подключает внешние инструменты. В AgentBox он работает внутри отдельной песочницы. В шаблоне claude CLI уже установлен.

Подключение

Установите SDK AgentBox и задайте AGENTBOX_API_KEY и ANTHROPIC_API_KEY в окружении приложения. Пример передаёт ключ Anthropic только команде агента. --model sonnet выбирает модель по алиасу Claude Code. Вместо него можно указать полное имя модели, доступной вашему ключу.

Для доступа к модели можно использовать HTTPS-прокси AgentBox https://sandbox-proxy.agentbox.ru:65181.

Переменные HTTPS_PROXY, HTTP_PROXY и NO_PROXY передаются только команде агента и наследуются её дочерними процессами. Это общие настройки сети процесса: другие запросы агента тоже могут идти через прокси. NO_PROXY оставляет соединения с localhost и 127.0.0.1 прямыми.

Сравните прайс-листы

Пример загружает два CSV с ценами поставщика. Claude сопоставляет товары по артикулу и записывает changes.md: изменения в рублях и процентах, прежние цены и новые позиции. В этих данных A-100 дорожает на 200 ₽ (20%), B-200 не меняется, C-300 появляется впервые.

mjs
import { Sandbox } from "@abox-dev/sdk";

const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required");
const proxy =
  process.env.SANDBOX_PROXY_URL ?? "https://sandbox-proxy.agentbox.ru:65181";
const sandbox = await Sandbox.create("claude", { timeoutMs: 600_000 });
try {
  await sandbox.files.makeDir("/home/user/work");
  await sandbox.files.write(
    "/home/user/work/prices-before.csv",
    "sku,price_rub\nA-100,1000\nB-200,2500\n",
  );
  await sandbox.files.write(
    "/home/user/work/prices-after.csv",
    "sku,price_rub\nA-100,1200\nB-200,2500\nC-300,800\n",
  );
  const handle = await sandbox.commands.run(
    "claude -p --model sonnet --dangerously-skip-permissions --output-format json",
    {
      cwd: "/home/user/work",
      timeoutMs: 300_000,
      background: true,
      stdin: true,
      envs: {
        ANTHROPIC_API_KEY: apiKey,
        HTTPS_PROXY: proxy,
        HTTP_PROXY: proxy,
        NO_PROXY: "localhost,127.0.0.1",
      },
      onStderr: (chunk) => process.stderr.write(chunk),
    },
  );
  await handle.sendStdin(
    "Compare prices-before.csv and prices-after.csv by sku. Write changes.md with price changes in RUB and percent, unchanged items, and new items. Use only the supplied data.",
  );
  await handle.closeStdin();
  const result = await handle.wait();
  const response = JSON.parse(result.stdout);
  if (response.is_error) throw new Error(response.result ?? response.subtype);
  console.log(response.result);
  console.log(
    "changes.md\n" + (await sandbox.files.read("/home/user/work/changes.md")),
  );
} finally {
  await sandbox.kill();
}

-p выполняет задание и завершает процесс. Приложение передаёт текст задания через stdin и закрывает ввод, затем дожидается результата. --dangerously-skip-permissions отключает запросы подтверждения и проверки разрешений Claude Code. Агент использует стандартный набор инструментов. Границей изоляции служит песочница AgentBox.

--output-format json возвращает текст в result, идентификатор session_id и признак ошибки is_error. Пример проверяет этот признак и читает готовый файл через SDK. Блок finally удаляет песочницу и при ошибке.

Получите результат и продолжите сессию

Следующий пример выводит ответ по мере генерации. Сначала Claude сравнивает прайс-листы, затем получает второй запрос: добавить стоимость закупки по 10 штук каждого прежнего товара. История и файлы остаются в той же песочнице.

mjs
import { Sandbox } from "@abox-dev/sdk";

const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required");
const proxy =
  process.env.SANDBOX_PROXY_URL ?? "https://sandbox-proxy.agentbox.ru:65181";
const sandbox = await Sandbox.create("claude", { timeoutMs: 600_000 });
try {
  await sandbox.files.makeDir("/home/user/work");
  await sandbox.files.write(
    "/home/user/work/prices-before.csv",
    "sku,price_rub\nA-100,1000\nB-200,2500\n",
  );
  await sandbox.files.write(
    "/home/user/work/prices-after.csv",
    "sku,price_rub\nA-100,1200\nB-200,2500\nC-300,800\n",
  );
  async function execute(prompt, sessionId) {
    let pending = "";
    let result;
    let nextSessionId;
    function event(message) {
      if (
        message.type === "stream_event" &&
        message.event.delta?.type === "text_delta"
      ) {
        process.stdout.write(message.event.delta.text);
      }
      if (message.type === "result") result = message;
    }
    function onStdout(chunk) {
      pending += chunk;
      let newline;
      while ((newline = pending.indexOf("\n")) !== -1) {
        const line = pending.slice(0, newline);
        pending = pending.slice(newline + 1);
        if (line.trim()) event(JSON.parse(line));
      }
    }
    const handle = await sandbox.commands.run(
      "claude -p --model sonnet --dangerously-skip-permissions --output-format stream-json --verbose --include-partial-messages" +
        (sessionId ? ' --resume "$AGENT_SESSION_ID"' : ""),
      {
        cwd: "/home/user/work",
        timeoutMs: 300_000,
        background: true,
        stdin: true,
        envs: {
          ANTHROPIC_API_KEY: apiKey,
          HTTPS_PROXY: proxy,
          HTTP_PROXY: proxy,
          NO_PROXY: "localhost,127.0.0.1",
          AGENT_SESSION_ID: sessionId ?? "",
        },
        onStdout,
        onStderr: (chunk) => process.stderr.write(chunk),
      },
    );
    await handle.sendStdin(prompt);
    await handle.closeStdin();
    await handle.wait();
    if (pending.trim()) event(JSON.parse(pending));
    if (!result) throw new Error("Claude Code ended without a result");
    if (result.is_error)
      throw new Error(
        result.result ?? result.errors?.join("\n") ?? result.subtype,
      );
    nextSessionId = result.session_id;
    if (!nextSessionId)
      throw new Error("Claude Code did not return a session ID");
    console.log("\nSession:", nextSessionId);
    return nextSessionId;
  }
  const sessionId = await execute(
    "Compare prices-before.csv and prices-after.csv by sku. Write changes.md with price changes in RUB and percent, unchanged items, and new items.",
  );
  await execute(
    "Add a procurement summary to changes.md: calculate the cost of 10 units of each existing item before and after the change. Keep the original comparison.",
    sessionId,
  );
  console.log(
    "changes.md\n" + (await sandbox.files.read("/home/user/work/changes.md")),
  );
} finally {
  await sandbox.kill();
}

--output-format stream-json --verbose --include-partial-messages включает поток JSONL. Обработчик собирает целые строки из фрагментов stdout и выводит stream_eventevent.delta.text, если тип фрагмента — text_delta.

Финальное событие result содержит is_error и session_id. После успешного завершения команды приложение передаёт этот ID в --resume и отправляет новое задание через stdin. Второй процесс продолжает тот же разговор. Песочница удаляется после чтения дополненного changes.md.

Для этих данных стоимость 10 штук A-100 и B-200 вырастет с 35 000 ₽ до 37 000 ₽. Новая позиция C-300 остаётся в сравнении, но не входит в этот расчёт. Другие параметры описаны в руководстве Claude Code.