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

Qwen Code

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

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

Qwen Code — агент для работы с файлами и командами с выбором провайдера модели. В AgentBox он доступен в готовом шаблоне qwen. Один и тот же SDK управляет входными файлами, процессом CLI и результатом.

Подключение

Установите SDK AgentBox и задайте AGENTBOX_API_KEY.

Примеры используют модель qwen/qwen3-coder-next через OpenRouter. Приложение читает OPENROUTER_API_KEY и передаёт его Qwen в OPENAI_API_KEY, потому что выбран OpenAI-совместимый протокол. OPENAI_BASE_URL указывает на https://openrouter.ai/api/v1, а OPENAI_MODEL задаёт модель. Флаг --auth-type openai выбирает способ подключения без интерактивного входа.

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

В примерах HTTPS_PROXY, HTTP_PROXY и NO_PROXY передаются процессу агента. Эти настройки могут влиять и на другие запросы агента и его дочерних процессов. Соединения с localhost и 127.0.0.1 остаются прямыми.

Соберите список неоплаченных счетов

Пример загружает выгрузку счетов. Qwen записывает сводку по неоплаченным счетам в receivables.md, после чего приложение читает её через SDK.

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

const apiKey = process.env.OPENROUTER_API_KEY;
if (!apiKey) throw new Error("OPENROUTER_API_KEY is required");
const proxy =
  process.env.SANDBOX_PROXY_URL ?? "https://sandbox-proxy.agentbox.ru:65181";
const sandbox = await Sandbox.create("qwen", { timeoutMs: 600_000 });
try {
  await sandbox.files.makeDir("/home/user/work");
  await sandbox.files.write(
    "/home/user/work/invoices.csv",
    `invoice,customer,amount,currency,status
INV-101,Acme,1200,EUR,paid
INV-102,Northwind,850,EUR,unpaid
INV-103,Contoso,420,EUR,unpaid
`,
  );
  await sandbox.files.write(
    "/home/user/work/task.txt",
    "Read invoices.csv and write receivables.md: unpaid invoices grouped by customer and the total unpaid amount.",
  );
  const result = await sandbox.commands.run(
    "qwen --auth-type openai --approval-mode yolo < task.txt",
    {
      cwd: "/home/user/work",
      timeoutMs: 300_000,
      envs: {
        HTTPS_PROXY: proxy,
        HTTP_PROXY: proxy,
        NO_PROXY: "localhost,127.0.0.1",
        OPENAI_API_KEY: apiKey,
        OPENAI_BASE_URL: "https://openrouter.ai/api/v1",
        OPENAI_MODEL: "qwen/qwen3-coder-next",
      },
      onStderr: (chunk) => process.stderr.write(chunk),
    },
  );
  console.log(result.stdout);
  console.log(
    "receivables.md\n" +
      (await sandbox.files.read("/home/user/work/receivables.md")),
  );
} finally {
  await sandbox.kill();
}

Задание хранится в task.txt и передаётся CLI через stdin. Режим --approval-mode yolo разрешает все штатные инструменты без запросов подтверждения. Файлы и команды остаются внутри песочницы AgentBox.

Пример читает результат до удаления песочницы. finally освобождает её и при ошибке. В Go для очистки используется отдельный контекст.

События и следующий запрос

Пример запускает Qwen с --output-format stream-json --include-partial-messages, показывает фрагменты ответа и вызовы инструментов. Затем передаёт сообщение об оплате и продолжает сессию через --resume.

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

const apiKey = process.env.OPENROUTER_API_KEY;
if (!apiKey) throw new Error("OPENROUTER_API_KEY is required");
const proxy =
  process.env.SANDBOX_PROXY_URL ?? "https://sandbox-proxy.agentbox.ru:65181";
const sandbox = await Sandbox.create("qwen", { timeoutMs: 600_000 });
try {
  await sandbox.files.makeDir("/home/user/work");
  await sandbox.files.write(
    "/home/user/work/invoices.csv",
    `invoice,customer,amount,currency,status
INV-101,Acme,1200,EUR,paid
INV-102,Northwind,850,EUR,unpaid
INV-103,Contoso,420,EUR,unpaid
`,
  );
  async function execute(prompt, sessionId) {
    await sandbox.files.write("/home/user/work/task.txt", prompt);
    let pending = "",
      nextSessionId,
      terminal,
      failure;
    function event(message) {
      if (message.session_id) nextSessionId = message.session_id;
      if (
        message.type === "stream_event" &&
        message.event?.delta?.type === "text_delta"
      )
        process.stdout.write(message.event.delta.text);
      if (message.type === "assistant")
        for (const block of message.message.content) {
          if (block.type === "tool_use") console.log("Tool:", block.name);
        }
      if (message.type === "result") {
        terminal = message.subtype;
        if (message.is_error)
          failure = new Error(message.result ?? JSON.stringify(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));
      }
    }
    await sandbox.commands.run(
      "qwen --auth-type openai --approval-mode yolo --output-format stream-json --include-partial-messages" +
        (sessionId ? ' --resume "$AGENT_SESSION_ID"' : "") +
        " < task.txt",
      {
        cwd: "/home/user/work",
        timeoutMs: 300_000,
        envs: {
          HTTPS_PROXY: proxy,
          HTTP_PROXY: proxy,
          NO_PROXY: "localhost,127.0.0.1",
          OPENAI_API_KEY: apiKey,
          OPENAI_BASE_URL: "https://openrouter.ai/api/v1",
          OPENAI_MODEL: "qwen/qwen3-coder-next",
          AGENT_SESSION_ID: sessionId ?? "",
        },
        onStdout,
        onStderr: (chunk) => process.stderr.write(chunk),
      },
    );
    if (pending.trim()) event(JSON.parse(pending));
    if (failure) throw failure;
    if (terminal !== "success")
      throw new Error("Agent did not finish normally: " + terminal);
    if (!nextSessionId) throw new Error("Agent did not return a session ID");
    console.log("\nSession:", nextSessionId);
    return nextSessionId;
  }
  const sessionId = await execute(
    "Read invoices.csv and write receivables.md: unpaid invoices grouped by customer and the total unpaid amount.",
  );
  await execute(
    "Invoice INV-102 has now been paid. Update receivables.md to reflect this payment.",
    sessionId,
  );
  console.log(
    "receivables.md\n" +
      (await sandbox.files.read("/home/user/work/receivables.md")),
  );
} finally {
  await sandbox.kill();
}

Событие system с subtype: init сообщает session_id. Части ответа приходят в stream_eventevent.delta с типом text_delta. Сообщения assistant содержат вызовы инструментов в блоках tool_use. Завершение подтверждает result с subtype: success и is_error: false. Обработчик собирает строки JSON из частей stdout, поскольку один вызов callback не обязательно содержит целое событие.

История сессии и файлы находятся в песочнице. Для следующего вызова сохраните её вместе с ID сессии или поставьте на паузу. Полный протокол и параметры CLI описаны в документации Qwen Code.