Skip to content

Qwen Code

Image updates

Ready-made agent images are updated every morning with the current CLI version. To keep a fixed image version, pin a template version.

Qwen Code is an agent with file and command tools and a choice of model provider. AgentBox provides it in the ready-made qwen template. The same SDK manages input files, the CLI process, and its output.

Connect

Install the AgentBox SDK and set AGENTBOX_API_KEY.

The examples use qwen/qwen3-coder-next through OpenRouter. The application reads OPENROUTER_API_KEY and passes it to Qwen as OPENAI_API_KEY, because the selected protocol is OpenAI-compatible. OPENAI_BASE_URL points to https://openrouter.ai/api/v1, and OPENAI_MODEL selects the model. --auth-type openai configures authentication without interactive login.

For model access, you can use the AgentBox HTTPS proxy https://sandbox-proxy.agentbox.ru:65181.

The examples pass HTTPS_PROXY, HTTP_PROXY, and NO_PROXY to the agent process. These settings may also affect other requests made by the agent and its child processes. Connections to localhost and 127.0.0.1 remain direct.

List unpaid invoices

The example uploads an invoice export. Qwen writes an unpaid-invoice summary to receivables.md, which the application reads through the 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();
}

The task is stored in task.txt and passed to the CLI through stdin. --approval-mode yolo permits all standard tools without approval prompts. Files and commands stay inside the AgentBox sandbox.

The example reads the result before killing the sandbox. finally also releases it on error. The Go example uses a separate context for cleanup.

Events and the next request

The example starts Qwen with --output-format stream-json --include-partial-messages and displays response fragments and tool calls. It then reports a payment and continues the session with --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();
}

The system event with subtype: init provides session_id. Response fragments arrive in stream_eventevent.delta with type text_delta. assistant messages include tool invocations in tool_use blocks. A result with subtype: success and is_error: false confirms completion. The handler buffers JSON lines from stdout chunks, because a callback does not necessarily contain a complete event.

Session history and files are stored in the sandbox. For another call, keep it and the session ID, or pause it. The full protocol and CLI options are described in the Qwen Code documentation.