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.
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.
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_event → event.delta с типом text_delta. Сообщения assistant содержат вызовы инструментов в блоках tool_use. Завершение подтверждает result с subtype: success и is_error: false. Обработчик собирает строки JSON из частей stdout, поскольку один вызов callback не обязательно содержит целое событие.
История сессии и файлы находятся в песочнице. Для следующего вызова сохраните её вместе с ID сессии или поставьте на паузу. Полный протокол и параметры CLI описаны в документации Qwen Code.