npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qwen-code/sdk

v0.1.22

Published

TypeScript SDK for programmatic access to qwen-code CLI

Readme

@qwen-code/sdk

A minimum experimental TypeScript SDK for programmatic access to Qwen Code.

Feel free to submit a feature request/issue/PR.

Installation

npm install @qwen-code/sdk

Requirements

  • Node.js >= 22.0.0

From v0.1.1, the CLI is bundled with the SDK. So no standalone CLI installation is needed.

Quick Start

import { query } from '@qwen-code/sdk';

// Single-turn query
const result = query({
  prompt: 'What files are in the current directory?',
  options: {
    cwd: '/path/to/project',
  },
});

// Iterate over messages
for await (const message of result) {
  if (message.type === 'assistant') {
    console.log('Assistant:', message.message.content);
  } else if (message.type === 'result') {
    console.log('Result:', message.result);
  }
}

API Reference

query(config)

Creates a new query session with the Qwen Code.

Parameters

  • prompt: string | AsyncIterable<SDKUserMessage> - The prompt to send. Use a string for single-turn queries or an async iterable for multi-turn conversations.
  • options: QueryOptions - Configuration options for the query session.

QueryOptions

| Option | Type | Default | Description | | ------------------------ | -------------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cwd | string | process.cwd() | The working directory for the query session. Determines the context in which file operations and commands are executed. | | model | string | - | The AI model to use (e.g., 'qwen-max', 'qwen-plus', 'qwen-turbo'). Takes precedence over OPENAI_MODEL and QWEN_MODEL environment variables. | | pathToQwenExecutable | string | Bundled CLI | Path to the Qwen Code executable. Supports multiple formats: 'qwen' (native binary from PATH), '/path/to/qwen' (explicit path), '/path/to/cli.js' (Node.js bundle), 'node:/path/to/cli.js' (force Node.js runtime), 'bun:/path/to/cli.js' (force Bun runtime). If not provided, the SDK uses the bundled CLI included with the package. | | permissionMode | 'default' \| 'plan' \| 'auto-edit' \| 'auto' \| 'yolo' | 'default' | Permission mode controlling tool execution approval. See Permission Modes for details. | | canUseTool | CanUseTool | - | Custom permission handler for tool execution approval. Invoked when a tool requires confirmation. Must respond within 60 seconds or the request will be auto-denied. See Custom Permission Handler. | | env | Record<string, string> | - | Environment variables to pass to the Qwen Code process. Merged with the current process environment. | | systemPrompt | string \| QuerySystemPromptPreset | - | System prompt configuration for the main session. Use a string to fully override the built-in Qwen Code system prompt, or a preset object to keep the built-in prompt and append extra instructions. | | mcpServers | Record<string, McpServerConfig> | - | MCP (Model Context Protocol) servers to connect. Supports external servers (stdio/SSE/HTTP) and SDK-embedded servers. External servers are configured with transport options like command, args, url, httpUrl, etc. SDK servers use { type: 'sdk', name: string, instance: Server }. | | abortController | AbortController | - | Controller to cancel the query session. Call abortController.abort() to terminate the session and cleanup resources. | | debug | boolean | false | Enable debug mode for verbose logging from the CLI process. | | maxSessionTurns | number | -1 (unlimited) | Maximum number of conversation turns before the session automatically terminates. Must be an integer. A turn consists of a user message and an assistant response. | | coreTools | string[] | - | Uses the legacy coreTools / CLI --core-tools allowlist semantics. If specified, only matching core tools are registered for the session. This is the only allowlist-style option that restricts built-in tool registration; a whole-tool permissions.deny / excludeTools rule (and tools.disabled in settings.json) also removes a tool from the registry. permissions.allow in settings.json is pure auto-approval and never removes, demotes, or hides a tool (#10075). To keep a tool's schema out of the initial model request, use tools.eager in settings.json (requires restart, #9827) — tool_search, tool_call, structured_output, plan-mode lifecycle tools, task_stop, mcp__* and computer_use__* tools are exempt from that allowlist and keep their normal loading; tools demoted this way stay registered and reachable through tool_search + tool_call while both bridge tools are registered — when either is unregistered (tools.toolSearch.enabled: false denies both; a tool_search or tool_call deny rule, or a tools.disabled entry removes one) the demoted tools that remain hidden are not offered to the model and cannot be reached through the bridge for that session, and a warning is written to the CLI process's stderr (SDK forwards it only with piped stderr and effective debug logging; an explicit logLevel always wins). These bridge and warning rules apply to direct tool mode. CodeModeOnly finds deferred schemas with top-level tool_search and calls them through exec; tools.eager also shrinks the initial exec description. Without search in scope, exec lists each allowed tool's signature. In direct mode they stay registered, so a direct call by their own name is still evaluated and approved normally — except tools also listed in tools.visible, which are declared upfront, and sessions whose live history contains a direct call to a still-hidden demoted tool, which any tool-set refresh (resume, MCP discovery, the first plan-mode entry in a session, a subagent definition change) re-declares.; to remove a tool entirely, use a whole-tool excludeTools / permissions.deny rule — a rule with a specifier (such as 'Bash(rm *)') only denies matching invocations at runtime. MCP tools are exempt from deny-based removal: hide them with the per-server excludeTools / tools.disabled filters instead (deny still blocks their calls at runtime). Example: ['read_file', 'edit', 'run_shell_command']. | | excludeTools | string[] | - | Equivalent to permissions.deny in settings.json. Excluded tools return a permission error immediately. Takes highest priority over all other permission settings. Supports tool name aliases and pattern matching: tool name ('write_file'), shell command prefix ('Bash(rm *)'), or path patterns ('Read(.env)', 'Edit(/src/**)'). | | allowedTools | string[] | - | Equivalent to permissions.allow in settings.json for auto-approval. Matching tools bypass canUseTool callback and execute automatically. Only applies when tool requires confirmation. Like permissions.allow, this is pure auto-approval and never affects which tools are registered or which schemas are sent (#10075). Supports same pattern matching as excludeTools. Example: ['Bash(git status)', 'Bash(npm test)']. | | authType | 'openai' \| 'anthropic' \| 'qwen-oauth' \| 'gemini' \| 'vertex-ai' | - | Authentication type for the AI service. When provided, the SDK forwards it to the CLI as --auth-type. | | agents | SubagentConfig[] | - | Configuration for subagents that can be invoked during the session. Subagents are specialized AI agents for specific tasks or domains. | | includePartialMessages | boolean | false | When true, the SDK emits incomplete messages as they are being generated, allowing real-time streaming of the AI's response. | | resume | string | - | Resume a previous session by providing its session ID. Equivalent to CLI's --resume flag. | | sessionId | string | - | Specify a session ID for the new session. Ensures SDK and CLI use the same ID without resuming history. Equivalent to CLI's --session-id flag. |

[!tip] If you need to configure coreTools, excludeTools, or allowedTools, it is strongly recommended to read the permissions configuration documentation first, especially the Tool name aliases and Rule syntax examples sections. Rule patterns such as Bash(git *), Read(.env), and Edit(/src/**) apply to excludeTools and allowedTools; coreTools accepts aliases but strips invocation specifiers.

Empty core-tool allowlists

An omitted or empty coreTools list is treated as unspecified; coreTools: [] does not create a tool-free session. A non-empty list restricts core tools, not every tool category: MCP and other non-core tools have separate registration and permission rules. Use excludeTools to exclude named tools explicitly. allowedTools controls approval, not which tools are registered.

Timeouts

The SDK enforces the following default timeouts:

| Timeout | Default | Description | | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | canUseTool | 1 minute | Maximum time for canUseTool callback to respond. If exceeded, the tool request is auto-denied. | | mcpRequest | 1 minute | Maximum time for SDK MCP tool calls to complete. | | controlRequest | 1 minute | Maximum time for control operations like initialize(), setModel(), setPermissionMode(), getContextUsage(), and interrupt() to complete. | | streamClose | 1 minute | Maximum time to wait for initialization to complete before closing CLI stdin in multi-turn mode with SDK MCP servers. |

You can customize these timeouts via the timeout option:

import { query } from '@qwen-code/sdk';

const q = query({
  prompt: 'Your prompt',
  options: {
    timeout: {
      canUseTool: 60000, // 60 seconds for permission callback
      mcpRequest: 600000, // 10 minutes for MCP tool calls
      controlRequest: 60000, // 60 seconds for control requests
      streamClose: 15000, // 15 seconds for stream close wait
    },
  },
});

Experimental Daemon Session Client

DaemonSessionClient is an experimental wrapper for clients that talk to a running qwen serve daemon over HTTP + SSE. It binds one daemon session so TUI, channel, IDE, or web backend adapters do not need to pass sessionId into every call.

import { DaemonClient, DaemonSessionClient } from '@qwen-code/sdk';

const daemon = new DaemonClient({
  baseUrl: 'http://127.0.0.1:4170',
  token: process.env['QWEN_SERVER_TOKEN'],
});

const caps = await daemon.capabilities();
const session = await DaemonSessionClient.createOrAttach(daemon, {
  workspaceCwd: caps.workspaceCwd,
});

const eventController = new AbortController();
const eventTask = (async () => {
  for await (const event of session.events({
    signal: eventController.signal,
  })) {
    console.log(event.type, event.data);
  }
})();

const result = await session.prompt({
  prompt: [{ type: 'text', text: 'Summarize this workspace.' }],
});

eventController.abort();
await eventTask;
console.log(result.stopReason);

session.events() tracks the last seen SSE event id and reuses it on the next subscription by default. Pass { resume: false } to start a fresh subscription without sending Last-Event-ID.

When createOrAttach() is called with modelServiceId, the returned session client seeds its first event subscription with Last-Event-ID: 0. This replays the daemon ring from the oldest available event so adapters can observe attach-time model_switch_failed or model_switched events that are not reported on the create/attach HTTP response. Raw DaemonClient callers should pass { lastEventId: 0 } on their first subscribeEvents() call when they use modelServiceId.

The raw event envelope remains available as DaemonEvent with data: unknown. Adapters that want a v1 typed view can layer the schema helpers on top without changing the wire stream:

import {
  asKnownDaemonEvent,
  createDaemonSessionViewState,
  reduceDaemonSessionEvent,
} from '@qwen-code/sdk';

let view = createDaemonSessionViewState();
for await (const event of session.events()) {
  view = reduceDaemonSessionEvent(view, event);

  const known = asKnownDaemonEvent(event);
  if (known?.type === 'permission_request') {
    console.log(known.data.requestId);
  }
}

Offline daemon transcript projection

The opt-in browser-safe transcript entry converts already-parsed append-only ChatRecord values into the same block model used by daemon clients. It does not read files, parse JSONL text, start a daemon, or access network or browser storage.

import { projectChatRecordsToDaemonTranscript } from '@qwen-code/sdk/daemon/transcript';

const projection = projectChatRecordsToDaemonTranscript(records);
if (!projection.complete) {
  console.warn(projection.diagnostics);
}
renderTranscript(projection.blocks);

Projection is synchronous and scans all records and message parts. maxBlocks limits retained output blocks, not computation. As a conservative browser guideline, project up to roughly 1,000 ordinary records on the main thread; move larger or unusually text-heavy inputs to a Web Worker and call the same entry there.

Talking to running sessions

@qwen-code/sdk/peer lets a program that is not a Qwen Code session join the sessions running as the same user on the same machine — a voice front-end, a relay, a build watcher. The program shows up in qwen sessions ps, and in the list_agents of every session that takes part in cross-session messaging — its agents.crossSessionMessaging setting is on, and it was not started with --bare or --safe-mode — which is also what lets those sessions message it by name with send_message. It can message them back. It runs on Node only and needs nothing beyond Node itself.

import { PeerEndpoint } from '@qwen-code/sdk/peer';

const endpoint = await PeerEndpoint.start({
  name: 'voice-bridge',
  onMessage: (message) =>
    console.log(`${message.fromName}: ${message.content}`),
});

const [session] = await endpoint.list();
if (session) {
  const sent = await endpoint.send({
    to: session.address,
    content: 'What are you working on?',
  });
  if (sent.kind === 'sent') {
    const receipt = await endpoint.awaitReceipt(sent.msgId, { final: true });
    console.log(receipt?.status); // delivered, denied, refused, ...
  }
}

await endpoint.close();

A message like that is held for the session's user to review. To direct a session without that review, mint a controller token with qwen sessions controllers add --label voice-bridge, give it to the endpoint, and mark the sends that should present it:

const endpoint = await PeerEndpoint.start({
  name: 'voice-bridge',
  controllerToken: process.env['QWEN_CONTROLLER_TOKEN'],
});
await endpoint.send({
  to: 'my-app-3f',
  content: 'run the tests',
  controller: true,
});

Things to know:

  • A session has an inbox only while its agents.crossSessionMessaging setting is on, which it is by default, and it was not started with --bare or --safe-mode — those turn messaging off whatever the setting says. A session that turned it off does not appear in list(), and its own list_agents and send_message cannot see or reach the program either. qwen sessions ps lists the program regardless.
  • A message is delivered without review in exactly two cases: the send presents a controller token (controller: true), or its fromMode names the receiving session's own review class. fromMode is a claim nothing authenticates, so a program that is not a coding session should leave it out. Nothing in the record — not kind, not name — buys delivery. The receiving session's agents.crossSessionInbound setting outranks both: hold or refuse there wins over a controller token.
  • Mark only the sends meant to direct a session. Addresses are resolved from records any program running as you can write, so a controller send presents the token to whichever process's record answers to that address. A controller send to another peer endpoint is dropped unread, because an endpoint's inbox accepts only its own token.
  • The endpoint's inbox applies none of the protections a Qwen Code session applies to its own: no rate limit, no holds, and no duplicate window beyond the last 200 messages it answered. Every message is answered delivered and handed to onMessage as it arrives, so apply your own limits there if you need them. Without onMessage, every message is answered refused.
  • Call close() before exiting, including from your own signal handlers. A process killed without closing leaves its record behind until a Qwen Code session lists the directory and sees the process is gone.
  • UNIX domain sockets only: Windows is not supported yet.

The record schema, wire format and receipt states are documented in Cross-Session Protocol.

Message Types

The SDK provides type guards to identify different message types:

import {
  isSDKUserMessage,
  isSDKAssistantMessage,
  isSDKSystemMessage,
  isSDKResultMessage,
  isSDKPartialAssistantMessage,
} from '@qwen-code/sdk';

for await (const message of result) {
  if (isSDKAssistantMessage(message)) {
    // Handle assistant message
  } else if (isSDKResultMessage(message)) {
    // Handle result message
  }
}

Query Instance Methods

The Query instance returned by query() provides several methods:

const q = query({ prompt: 'Hello', options: {} });

// Get session ID
const sessionId = q.getSessionId();

// Check if closed
const closed = q.isClosed();

// Interrupt the current operation
await q.interrupt();

// Change permissio