Typescript SDK
@qwen-code/sdk
Ein minimales experimentelles TypeScript SDK für den programmatischen Zugriff auf Qwen Code.
Du kannst gerne einen Feature-Wunsch, ein Issue oder einen PR einreichen.
Installation
npm install @qwen-code/sdkVoraussetzungen
- Node.js >= 22.0.0
- Qwen Code >= 0.4.0 (stabil). Das SDK verwendet standardmäßig seine mitgelieferte CLI; setze
pathToQwenExecutablenur, wenn du eine eigeneqwen-Binärdatei oder ein CLI-Bundle ausführen möchtest.
Schnellstart
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-Referenz
query(config)
Erstellt eine neue Query-Sitzung mit Qwen Code.
Parameter
prompt:string | AsyncIterable<SDKUserMessage>– Der zu sendende Prompt. Verwende einen String für Single-Turn-Abfragen oder eine async iterable für Multi-Turn-Konversationen.options:QueryOptions– Konfigurationsoptionen für die Query-Sitzung.
QueryOptions
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
cwd | string | process.cwd() | Das Arbeitsverzeichnis für die Query-Sitzung. Bestimmt den Kontext, in dem Dateioperationen und Befehle ausgeführt werden. |
model | string | - | Das zu verwendende KI-Modell (z. B. 'qwen-max', 'qwen-plus', 'qwen-turbo'). Überschreibt die Umgebungsvariablen OPENAI_MODEL und QWEN_MODEL. |
pathToQwenExecutable | string | Mitgelieferte CLI | Pfad zur ausführbaren Qwen Code-Datei. Unterstützt mehrere Formate: 'qwen' (native Binärdatei aus PATH), '/pfad/zu/qwen' (expliziter Pfad), '/pfad/zu/cli.js' (Node.js-Bundle), 'node:/pfad/zu/cli.js' (Node.js-Laufzeitumgebung erzwingen), 'bun:/pfad/zu/cli.js' (Bun-Laufzeitumgebung erzwingen). Wenn nicht angegeben, verwendet das SDK die mit dem Paket mitgelieferte CLI. |
permissionMode | 'default' | 'plan' | 'auto-edit' | 'auto' | 'yolo' | 'default' | Berechtigungsmodus zur Steuerung der Ausführungsgenehmigung von Tools. Siehe Berechtigungsmodi für Details. |
canUseTool | CanUseTool | - | Benutzerdefinierter Berechtigungs-Handler für die Genehmigung der Tool-Ausführung. Wird aufgerufen, wenn ein Tool eine Bestätigung benötigt. Muss innerhalb von 60 Sekunden antworten, andernfalls wird die Anfrage automatisch abgelehnt. Siehe Benutzerdefinierter Berechtigungs-Handler. |
env | Record<string, string> | - | Umgebungsvariablen, die an den Qwen Code-Prozess übergeben werden. Werden mit der aktuellen Prozessumgebung zusammengeführt. |
systemPrompt | string | QuerySystemPromptPreset | - | System-Prompt-Konfiguration für die Hauptsitzung. Verwende einen String, um den eingebauten Qwen Code-System-Prompt vollständig zu überschreiben, oder ein Preset-Objekt, um den eingebauten Prompt zu behalten und zusätzliche Anweisungen anzuhängen. |
mcpServers | Record<string, McpServerConfig> | - | MCP-Server (Model Context Protocol), mit denen verbunden werden soll. Unterstützt externe Server (stdio/SSE/HTTP) und SDK-eingebettete Server. Externe Server werden mit Transport-Optionen wie command, args, url, httpUrl usw. konfiguriert. SDK-Server verwenden { type: 'sdk', name: string, instance: Server }. |
abortController | AbortController | - | Controller zum Abbrechen der Query-Sitzung. Rufe abortController.abort() auf, um die Sitzung zu beenden und Ressourcen freizugeben. |
debug | boolean | false | Aktiviert den Debug-Modus für ausführliche Protokollierung durch den CLI-Prozess. |
maxSessionTurns | number | -1 (unbegrenzt) | Maximale Anzahl von Konversationsdurchläufen, bevor die Sitzung automatisch beendet wird. Muss eine ganze Zahl sein. Ein Durchlauf besteht aus einer Benutzernachricht und einer Assistant-Antwort. |
coreTools | string[] | - | Verwendet die alte coreTools-/CLI --core-tools-Allowlist-Semantik. Wenn angegeben, werden nur passende Core-Tools für die Sitzung registriert. Dies ist die einzige Allowlist-Option, die die Registrierung eingebauter Tools einschränkt; eine Whole-Tool permissions.deny-/excludeTools-Regel (und tools.disabled in settings.json) entfernt ein Tool ebenfalls aus der Registry. permissions.allow in settings.json ist reine Auto-Genehmigung und entfernt, degradiert oder versteckt niemals ein Tool (#10075). Um das Schema eines Tools aus der anfänglichen Modell-Anfrage herauszuhalten, verwende tools.eager in settings.json (erfordert Neustart, #9827) — tool_search, structured_output, Plan-Mode-Lifecycle-Tools, task_stop, mcp__* und computer_use__*-Tools sind von dieser Allowlist ausgenommen und behalten ihr normales Laden; um es vollständig zu entfernen, verwende eine Whole-Tool excludeTools-/permissions.deny-Regel – eine Regel mit einem Spezifizierer (wie 'Bash(rm *)') verweigert nur passende Aufrufe zur Laufzeit. MCP-Tools sind von der deny-basierten Entfernung ausgenommen: Verstecke sie stattdessen mit den serverbezogenen excludeTools-/tools.disabled-Filtern (deny blockiert weiterhin deren Aufrufe zur Laufzeit). Beispiel: ['read_file', 'edit', 'run_shell_command']. |
excludeTools | string[] | - | Entspricht permissions.deny in settings.json. Ausgeschlossene Tools geben sofort einen Berechtigungsfehler zurück. Hat höchste Priorität gegenüber allen anderen Berechtigungseinstellungen. Unterstützt Toolnamen-Alias und Mustervergleich: Toolname ('write_file'), Shell-Befehlspräfix ('Bash(rm *)') oder Pfadmuster ('Read(.env)', 'Edit(/src/**)'). |
allowedTools | string[] | - | Entspricht permissions.allow in settings.json für die Auto-Genehmigung. Passende Tools umgehen den canUseTool-Callback und werden automatisch ausgeführt. Gilt nur, wenn das Tool eine Bestätigung erfordert. Wie permissions.allow ist dies reine Auto-Genehmigung und beeinflusst niemals, welche Tools registriert sind oder welche Schemas gesendet werden (#10075). Unterstützt denselben Mustervergleich wie excludeTools. Beispiel: ['Bash(git status)', 'Bash(npm test)']. |
authType | 'openai' | 'anthropic' | 'qwen-oauth' | 'gemini' | 'vertex-ai' | - | Authentifizierungstyp für den KI-Dienst. Wenn angegeben, leitet das SDK ihn als --auth-type an die CLI weiter. |
agents | SubagentConfig[] | - | Konfiguration für Subagenten, die während der Sitzung aufgerufen werden können. Subagenten sind spezialisierte KI-Agenten für bestimmte Aufgaben oder Bereiche. |
includePartialMessages | boolean | false | Wenn true, sendet das SDK unvollständige Nachrichten während der Generierung, was Echtzeit-Streaming der KI-Antwort ermöglicht. |
resume | string | - | Setze eine vorherige Sitzung durch Angabe ihrer Sitzungs-ID fort. Entspricht dem --resume-Flag der CLI. |
sessionId | string | - | Gib eine Sitzungs-ID für die neue Sitzung an. Stellt sicher, dass SDK und CLI dieselbe ID verwenden, ohne den Verlauf fortzusetzen. Entspricht dem --session-id-Flag der CLI. |
[!note] Bei
coreToolsfunktionieren auch Aliase wieRead,EditundBash, aber Aufrufspezifizierer wieBash(git *)werden entfernt.coreToolsschränkt die Tool-Registrierung ein, nicht die Aufrufmuster.
Timeouts
Das SDK erzwingt die folgenden Standard-Timeout-Werte:
| Timeout | Standard | Beschreibung |
|---|---|---|
canUseTool | 1 Minute | Maximale Zeit für die Antwort des canUseTool-Callbacks. Bei Überschreitung wird die Tool-Anfrage automatisch abgelehnt. |
mcpRequest | 1 Minute | Maximale Zeit für den Abschluss von SDK-MCP-Toolaufrufen. |
controlRequest | 1 Minute | Maximale Zeit für den Abschluss von Steuerungsoperationen wie initialize(), setModel(), setPermissionMode(), getContextUsage() und interrupt(). |
streamClose | 1 Minute | Maximale Wartezeit für den Abschluss der Initialisierung vor dem Schließen von CLI stdin im Multi-Turn-Modus mit SDK-MCP-Servern. |
Du kannst diese Timeouts über die Option timeout anpassen:
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
},
},
});Nachrichtentypen
Das SDK bietet Typwächter zur Identifizierung verschiedener Nachrichtentypen:
import {
isSDKUserMessage,
isSDKAssistantMessage,
isSDKSystemMessage,
isSDKResultMessage,
isSDKPartialAssistantMessage,
} from '@qwen-code/sdk';
for await (const message of result) {
if (isSDKAssistantMessage(message)) {
// Assistant-Nachricht verarbeiten
} else if (isSDKResultMessage(message)) {
// Ergebnisnachricht verarbeiten
}
}Methoden der Query-Instanz
Die von query() zurückgegebene Query-Instanz stellt mehrere Methoden bereit:
const q = query({ prompt: 'Hallo', options: {} });
// Sitzungs-ID abrufen
const sessionId = q.getSessionId();
// Prüfen, ob geschlossen
const closed = q.isClosed();
// Aktuelle Operation unterbrechen
await q.interrupt();
// Berechtigungsmodus während der Sitzung ändern
await q.setPermissionMode('yolo');
// Modell während der Sitzung ändern
await q.setModel('qwen-max');
// Nutzung des Kontextfensters abrufen (Token-Anzahl pro Kategorie)
const usage = await q.getContextUsage();
// true übergeben, um anzuzeigen, dass Details pro Element angezeigt werden sollen
const detail = await q.getContextUsage(true);
// Sitzung schließen
await q.close();interrupt() bricht nur den aktiven Turn ab. Bei einer Multi-Turn-Query, die mit einem asynchronen iterierbaren Prompt erstellt wurde, bleiben die Query und ihr Input-Stream offen, sodass spätere Nachrichten aus dem Iterable normal verarbeitet werden. Verwende close() oder breche den konfigurierten AbortController ab, wenn du die gesamte Session beenden möchtest.
Vom Caller bereitgestellte Session-IDs im Daemon
DaemonClient.createOrAttachSession akzeptiert eine optionale sessionId für Caller, die eine Identität vor der Session-Erstellung persistieren müssen:
import { DaemonClient } from '@qwen-code/sdk';
const daemon = new DaemonClient({ baseUrl: 'http://127.0.0.1:4170' });
const session = await daemon.createOrAttachSession({
workspaceCwd: '/path/to/project',
sessionId: '550E8400-E29B-41D4-A716-446655440000',
});
console.log(session.sessionId); // 550e8400-e29b-41d4-a716-446655440000Das SDK benötigt die session_id_override-Capability des Daemons vor dem Senden der Mutation. Der REST-Modus serialisiert sessionId direkt; ein aktiver ACP-Adapter mappt es auf session/new._meta["qwen-code/sessionId"]. Das SDK überprüft die Erfolgsantwort und wirft DaemonSessionIdProtocolError, wenn der Daemon eine andere ID zurückgibt.
Diese Option erzeugt immer eine neue Thread-Session und ist kein idempotentes Attach. Wenn das Ergebnis der Erstellung mehrdeutig ist, verwende die bekannte ID mit Load oder Resume. Das Weglassen der Option behält das bestehende Create-or-Attach-Verhalten bei.
Kommunikation mit laufenden Sessions
@qwen-code/sdk/peer ermöglicht es einem Programm, das keine Qwen Code-Session ist, sich den Sessions anzuschließen, die als derselbe Benutzer auf derselben Maschine laufen – ein Voice-Frontend, ein Relay, ein Build-Watcher. Das Programm erscheint in qwen sessions ps und in der list_agents jeder Session, die agents.crossSessionMessaging aktiviert hat – was auch ermöglicht, dass diese Sessions ihm per Namen mit send_message Nachrichten senden können. Es kann ihnen ebenfalls Nachrichten senden. Es läuft nur auf Node und benötigt nichts außer Node selbst.
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();Eine solche Nachricht wird zur Überprüfung durch den Benutzer der Session zurückgehalten. Um eine Session ohne diese Überprüfung zu steuern, erstelle ein Controller-Token mit qwen sessions controllers add --label voice-bridge, übergib es dem Endpoint und markiere die Sends, die es präsentieren sollen:
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,
});Wichtige Hinweise:
- Eine Session hat nur dann eine Inbox, wenn ihre
agents.crossSessionMessaging-Einstellung aktiviert ist, und diese Einstellung ist standardmäßig deaktiviert. Ohne sie erscheint die Session nicht inlist(), und ihre eigenenlist_agentsundsend_messagekönnen das Programm ebenfalls nicht sehen oder erreichen.qwen sessions pslistet das Programm unabhängig davon auf. - Eine Nachricht wird genau in zwei Fällen ohne Überprüfung zugestellt: Der Send präsentiert ein Controller-Token (
controller: true), oder seinfromModebenennt die eigene Review-Klasse der empfangenden Session.fromModeist eine Behauptung, die nichts authentifiziert, daher sollte ein Programm, das keine Coding-Session ist, es weglassen. Nichts im Record – nichtkind, nichtname– erzwingt die Zustellung. Dieagents.crossSessionInbound-Einstellung der empfangenden Session hat Vorrang vor beiden:holdoderrefusedort gewinnt gegenüber einem Controller-Token. - Markiere nur die Sends, die zur Steuerung einer Session bestimmt sind. Adressen werden aus Records aufgelöst, die jedes als dein Benutzer laufende Programm schreiben kann, daher präsentiert ein Controller-Send das Token demjenigen Prozess, dessen Record auf diese Adresse antwortet. Ein Controller-Send an einen anderen Peer-Endpoint wird ungelesen verworfen, da die Inbox eines Endpoints nur sein eigenes Token akzeptiert.
- Die Inbox des Endpoints wendet keinen der Schutzmechanismen an, die eine Qwen Code-Session auf ihre eigene anwendet: kein Rate-Limit, keine Holds und kein Duplicate-Window über die letzten 200 beantworteten Nachrichten hinaus. Jede Nachricht wird als
deliveredbeantwortet und bei Eingang anonMessageübergeben, also wende dort deine eigenen Limits an, wenn du sie benötigst. OhneonMessagewird jede Nachricht alsrefusedbeantwortet. - Rufe
close()vor dem Beenden auf, einschließlich aus deinen eigenen Signal-Handlern. Ein Prozess, der ohne Schließen getötet wird, hinterlässt seinen Record, bis eine Qwen Code-Session das Verzeichnis auflistet und sieht, dass der Prozess nicht mehr existiert. - Nur UNIX-Domain-Sockets: Windows wird noch nicht unterstützt.
Das Record-Schema, Wire-Format und Receipt-States sind dokumentiert in Cross-Session Protocol.
Berechtigungsmodi
Das SDK unterstützt verschiedene Berechtigungsmodi zur Steuerung der Tool-Ausführung:
default: Schreib-Tools werden abgelehnt, sofern sie nicht über dencanUseTool-Callback oder inallowedToolsgenehmigt werden. Schreibgeschützte Tools werden ohne Bestätigung ausgeführt.plan: Blockiert alle Schreib-Tools und weist die KI an, zuerst einen Plan vorzulegen.auto-edit: Bearbeitungstools (edit,write_file,notebook_edit) werden automatisch genehmigt, während andere Tools eine Bestätigung erfordern.auto: Verwendet den eingebauten Klassifikator, um sichere Tool-Aufrufe automatisch zu genehmigen und riskante zu blockieren, mit Fallback auf manuelle Genehmigung nach wiederholten Policy-Blockaden oder Klassifikator-Ausfällen.yolo: Alle Tools werden automatisch ohne Bestätigung ausgeführt.
Berechtigungsprioritätskette
Entscheidungspriorität (höchste zuerst): deny > ask > allow > (Standard-/Interaktivmodus)
Die erste passende Regel gewinnt.
excludeTools/permissions.deny– Blockiert Tools vollständig (gibt Berechtigungsfehler zurück)permissions.ask– Erfordert immer eine BenutzerbestätigungpermissionMode: 'plan'– Blockiert alle nicht schreibgeschützten ToolspermissionMode: 'yolo'– Genehmigt alle Tools automatischallowedTools/permissions.allow– Genehmigt passende Tools automatischpermissionMode: 'auto'– Klassifikator-vermittelte Genehmigung für verbleibende ToolscanUseTool-Callback – Benutzerdefinierte Genehmigungslogik (wenn angegeben, wird er nicht für genehmigte Tools aufgerufen)- Standardverhalten – Automatische Ablehnung im SDK-Modus (Schreib-Tools erfordern explizite Genehmigung)
Beispiele
Mehrfach-Dialog
import { query, type SDKUserMessage } from '@qwen-code/sdk';
async function* generateMessages(): AsyncIterable<SDKUserMessage> {
yield {
type: 'user',
session_id: 'my-session',
message: { role: 'user', content: 'Erstelle eine Datei hello.txt' },
parent_tool_use_id: null,
};
// Auf eine Bedingung oder Benutzereingabe warten
yield {
type: 'user',
session_id: 'my-session',
message: { role: 'user', content: 'Lies jetzt die Datei zurück' },
parent_tool_use_id: null,
};
}
const result = query({
prompt: generateMessages(),
options: {
permissionMode: 'auto-edit',
},
});
for await (const message of result) {
console.log(message);
}Benutzerdefinierter Berechtigungs-Handler
import { query, type CanUseTool } from '@qwen-code/sdk';
const canUseTool: CanUseTool = async (toolName, input, { signal }) => {
// Alle Leseoperationen erlauben
if (toolName.startsWith('read_')) {
return { behavior: 'allow', updatedInput: input };
}
// Benutzer bei Schreiboperationen um Bestätigung bitten (in einer echten Anwendung)
const userApproved = await promptUser(`Erlaube ${toolName}?`);
if (userApproved) {
return { behavior: 'allow', updatedInput: input };
}
return { behavior: 'deny', message: 'Benutzer hat die Operation abgelehnt' };
};
const result = query({
prompt: 'Erstelle eine neue Datei',
options: {
canUseTool,
},
});Mit externen MCP-Servern
import { query } from '@qwen-code/sdk';
const result = query({
prompt: 'Verwende das benutzerdefinierte Tool von meinem MCP-Server',
options: {
mcpServers: {
'mein-server': {
command: 'node',
args: ['pfad/zu/mcp-server.js'],
env: { PORT: '3000' },
},
},
},
});System-Prompt überschreiben
import { query } from '@qwen-code/sdk';
const result = query({
prompt: 'Sag Hallo in einem Satz.',
options: {
systemPrompt: 'Du bist ein knapper Assistent. Antworte in genau einem Satz.',
},
});An den eingebauten System-Prompt anhängen
import { query } from '@qwen-code/sdk';
const result = query({
prompt: 'Überprüfe das aktuelle Verzeichnis.',
options: {
systemPrompt: {
type: 'preset',
preset: 'qwen_code',
append: 'Sei knapp und konzentriere dich auf konkrete Ergebnisse.',
},
},
});Mit SDK-eingebetteten MCP-Servern
Das SDK bietet tool und createSdkMcpServer, um MCP-Server zu erstellen, die im selben Prozess wie deine SDK-Anwendung laufen. Dies ist nützlich, wenn du benutzerdefinierte Tools für die KI bereitstellen möchtest, ohne einen separaten Serverprozess auszuführen.
tool(name, description, inputSchema, handler)
Erstellt eine Tool-Definition mit Typinferenz über Zod-Schema.
| Parameter | Typ | Beschreibung |
|---|---|---|
name | string | Tool-Name (1-64 Zeichen, beginnt mit Buchstaben, alphanumerisch und Unterstriche) |
description | string | Für Menschen lesbare Beschreibung der Funktion des Tools |
inputSchema | ZodRawShape | Zod-Schema-Objekt, das die Eingabeparameter des Tools definiert |
handler | (args, extra) => Promise<Result> | Asynchrone Funktion, die das Tool ausführt und MCP-Inhaltsblöcke zurückgibt |
Der Handler muss ein CallToolResult-Objekt mit folgender Struktur zurückgeben:
{
content: Array<
| { type: 'text'; text: string }
| { type: 'image'; data: string; mimeType: string }
| { type: 'resource'; uri: string; mimeType?: string; text?: string }
>;
isError?: boolean;
}createSdkMcpServer(options)
Erstellt eine SDK-eingebettete MCP-Serverinstanz.
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
name | string | Erforderlich | Eindeutiger Name für den MCP-Server |
version | string | '1.0.0' | Serverversion |
tools | SdkMcpToolDefinition[] | - | Array von Tools, die mit tool() erstellt wurden |
Gibt ein McpSdkServerConfigWithInstance-Objekt zurück, das direkt an die mcpServers-Option übergeben werden kann.
Beispiel
import { z } from 'zod';
import { query, tool, createSdkMcpServer } from '@qwen-code/sdk';
// Definiere ein Tool mit Zod-Schema
const calculatorTool = tool(
'calculate_sum',
'Addiere zwei Zahlen',
{ a: z.number(), b: z.number() },
async (args) => ({
content: [{ type: 'text', text: String(args.a + args.b) }],
}),
);
// Erstelle den MCP-Server
const server = createSdkMcpServer({
name: 'calculator',
tools: [calculatorTool],
});
// Verwende den Server in einer Abfrage
const result = query({
prompt: 'Was ist 42 + 17?',
options: {
permissionMode: 'yolo',
mcpServers: {
calculator: server,
},
},
});
for await (const message of result) {
console.log(message);
}Eine Abfrage abbrechen
import { query, isAbortError } from '@qwen-code/sdk';
const abortController = new AbortController();
const result = query({
prompt: 'Lang laufende Aufgabe...',
options: {
abortController,
},
});
// Abbruch nach 5 Sekunden
setTimeout(() => abortController.abort(), 5000);
try {
for await (const message of result) {
console.log(message);
}
} catch (error) {
if (isAbortError(error)) {
console.log('Abfrage wurde abgebrochen');
} else {
throw error;
}
}Fehlerbehandlung
Das SDK stellt eine AbortError-Klasse zur Behandlung abgebrochener Abfragen bereit:
import { AbortError, isAbortError } from '@qwen-code/sdk';
try {
// ... Abfrageoperationen
} catch (error) {
if (isAbortError(error)) {
// Abort behandeln
} else {
// Andere Fehler behandeln
}
}