Skip to Content
Руководство для разработчиковРуководство по интеграции REST API

Руководство по интеграции через REST API

Для команд, встраивающих Qwen Code в собственный продукт поверх HTTP: запустите qwen serve как бэкенд и управляйте им из собственного фронтенда.

Эта страница — отправная точка. Курируемый справочник Daemon REST API описывает стабильную поверхность интеграции и ссылается на контракт OpenAPI 3.1. Полный протокол находится в qwen-serve-protocol.md; внутреннее устройство описано в углублённом руководстве по демону; рабочий пример на TypeScript — в examples/daemon-client-quickstart.md.

Какие пути существуют

Шесть способов построения на базе демона, разделённых одним вопросом — какую часть фронтенда вы контролируете?

ПутьВы контролируетеСтатус
демон + встроенный Web Shellничего — используете как естьдоступен сейчас (руководство пользователя)
демон --no-web + ваш собственный UIвесь фронтенд целикомдоступен сейчас — эта страница
демон + брендированный Web Shellбрендинг, а не кодне реализовано (#11357 )
демон + самостоятельно собранный Web Shellсборку фронтендане реализовано (#11358 )
демон через SDK DaemonClientклиентский код, без сырого HTTPдоступен сейчас (TS, Java) — Python SDK работает только с процесс-транспортом и не имеет клиента демона, поэтому Python-интеграция использует путь 2 через сырой HTTP
демон через MCP bridgeничего — другой агент управляет импоставляется как qwen-serve-mcp в @qwen-code/sdk — см. README бриджа ; QWEN_BRIDGE_ALLOW_GLOBAL_SCOPE опционально разрешает мутации в глобальной области

Headless qwen -p и ACP через stdio для редакторов — это отдельные пути интеграции. Каналы и расширения также могут работать через демон; см. руководство по каналам и справочник расширений.

Что нужно знать перед проектированием

Демон не выполняет инференс внутри своего процесса. Он порождает дочерние процессы qwen --acp и выступает посредником между ними и HTTP. Он запускает входной скрипт CLI под тем же бинарём Node, используя QWEN_CLI_ENTRY или, иначе, process.argv[1]. Встраиваемый Node-бэкенд должен указывать в QWEN_CLI_ENTRY путь к установленному входному скрипту Qwen CLI; поиск qwen в PATH не производится. Отсутствие точки входа проявляется как MissingCliEntryError.

В установившемся режиме существует один дочерний процесс на активное рабочее пространство, а не по одному на сессию. Каждая сессия в рабочем пространстве мультиплексируется на этот дочерний процесс и разделяет его процесс, состояние OAuth, файловый кэш и разбор иерархической памяти. Таким образом, домен отказа — это рабочее пространство: если дочерний процесс завершается, все мультиплексированные на него сессии уничтожаются вместе. Рассчитывайте контейнер на демон плюс один дочерний процесс на зарегистрированное рабочее пространство, с запасом на один дополнительный дочерний процесс на среду выполнения при переключении канала. Когда сессии должны отказывать независимо, запускайте отдельные демоны — --max-sessions ограничивает параллелизм, а не радиус поражения.

Аутентификация рассчитана на одного оператора. Runtime bearer-токен предоставляет доступ ко всему bearer-защищённому API, а доверенный loopback-вызывающий получает полные полномочия, включая выполнение кода от имени пользователя демона. Модели принципала для каждого конечного пользователя нет. Если вы размещаете это за мультипользовательским продуктом, ваш бэкенд отвечает за идентификацию пользователей и не должен передавать токен демона в браузеры. Контейнеризированное и мультитенантное развёртывание явно отложено — см. «v0.16-alpha known limits» в руководстве пользователя.

Настроенный вход webhook канала (POST /channels/:channelName/webhooks/:source) использует собственную аутентификацию x-qwen-webhook-secret до bearer-аутентификации; он неактивен, пока не настроен источник webhook канала.

Запуск демона

Сгенерируйте токен один раз в терминале 1. Встроенная команда оболочки выводит его, чтобы вы могли вставить то же значение в скрытый запрос, показанный ниже, в каждом остальном терминале:

export QWEN_SERVER_TOKEN="$(openssl rand -hex 32)" printf 'Copy this token to the other terminals: %s\n' "$QWEN_SERVER_TOKEN" export DAEMON_URL=http://127.0.0.1:4170

Терминал 1 — эта команда блокирует, поэтому оставьте её работающей:

qwen serve --no-web --require-auth \ --hostname 0.0.0.0 --port 4170 \ --workspace /srv/project

В каждом другом терминале вставьте токен, выведенный терминалом 1, когда read запросит его. Это сохраняет токен вне истории оболочки и аргументов дочерних процессов:

read -rsp 'QWEN_SERVER_TOKEN: ' QWEN_SERVER_TOKEN; printf '\n' export QWEN_SERVER_TOKEN export DAEMON_URL=http://127.0.0.1:4170

DAEMON_URL — это loopback-базовый URL, который использует каждая клиентская команда ниже — экспортируйте его с тем же значением в каждом терминале, где вы их запускаете — и он совпадает с servers[0].url в артефакте OpenAPI. Демон всё ещё привязывается к 0.0.0.0, чтобы удалённый хост мог достичь его, но не направляйте DAEMON_URL на этот хост в открытом виде: bearer-токен, который может управлять оболочкой, читается любым на пути. Подключайтесь к не-loopback хосту через TLS (см. ниже).

--no-web сохраняет перечисленные ниже маршруты, но отключает ресурсы Web Shell и зависимые поверхности: на macOS — маршруты /live/* и сокет /live/host, а на каждой платформе — GET /mcp-app-sandbox. Передавайте токен через переменную окружения, а не через --token, который доступен любому локальному пользователю через /proc/<pid>/cmdline.

Примеры Bash ниже передают заголовок Authorization через файловый дескриптор с помощью встроенной команды printf оболочки, не включая токен в аргументы curl. Они требуют Bash, curl и jq. Для доступа с других устройств завершите TLS, как описано в HTTPS / TLS для мобильного и кросс-устройственного доступа; демон затем обслуживает https:// на том же порту, поэтому переэкспортируйте DAEMON_URL со схемой https:// перед запуском команд ниже.

Маршруты, которые реально использует интеграция

Большая часть того, что регистрирует демон, существует для работы Web Shell — операции git, установка расширений, доверие к рабочему пространству, голос, запланированные задачи — и меняется вместе с этим UI. Нижеприведённое подмножество на порядок меньше.

Это то, что нужно REST-интеграции. Остальное считайте внутренним.

Обнаружение

МаршрутНазначение
GET /healthLiveness-проба
GET /capabilitiesПредварительный запрос — прочитайте workspaceCwd и policy.permission до всего остального

Жизненный цикл сессии

МаршрутНазначение
POST /sessionСоздание. Отправьте sessionScope: "thread" для независимого разговора
DELETE /session/:idЗакрытие. Сохранённая сессия остаётся и может быть перезагружена
POST /session/:id/load · POST /session/:id/resumeВосстановление сохранённой сессии
POST /session/:id/heartbeatОтсрочка idle-утилизатора
PATCH /session/:id/metadataМетаданные сессии
POST /session/:id/modelПереключение модели в пределах привязанной службы
GET /session/:id/statusСтатус среды выполнения

Промптинг и потоковая передача

МаршрутНазначение
POST /session/:id/promptОтправка. Возвращает 202 при допуске, а не при завершении
POST /session/:id/cancelОтмена только активного промпта
GET /session/:id/eventsПоток SSE. Подписывайтесь до отправки промпта
GET /session/:id/transcriptИстория разговора
GET /session/:id/contextМодель верхнего уровня, режим и состояние параметров конфигурации; виртуальные субагенты возвращают пустой state
GET /session/:id/export · GET /session/:id/pending-promptsЭкспорт сохранённого транскрипта · список промптов в очереди

Разрешения

МаршрутНазначение
POST /session/:id/permission/:requestIdОтвет на permission_request. Маршрутизируется к среде выполнения, владеющей сессией, и никогда не возвращается к первичному бриджу; недоверенный не-основной владелец отклоняется, тогда как недоверенный основной владелец переходит к активной политике разрешений
POST /permission/:requestIdПроцесс-глобальная форма, подключённая только к бриджу основного рабочего пространства: возвращает 404 для сессии, принадлежащей другой зарегистрированной среде выполнения, с тем же телом, что и пропавший голос при политике first-responder по умолчанию — поэтому 404 здесь сам по себе не означает, что запрос уже был отвечен

Контекст рабочего пространства только для чтения

МаршрутНазначение
GET /file · /file/bytesЧтение файла или диапазона байт
GET /stat · GET /list · GET /globМетаданные пути, список каталога, glob
GET /workspace/toolsИнструменты, сообщённые активным дочерним ACP-процессом; без него ответ содержит acpChannelLive: false, tools: [] и ошибку not_started

Минимальный поток

1. Предварительный запрос. Прочитайте workspaceCwd (чтобы не передавать cwd при создании) и policy.permission (чтобы знать, кто может отвечать на запросы разрешений).

curl -sH @<(printf 'Authorization: Bearer %s\n' "$QWEN_SERVER_TOKEN") "$DAEMON_URL/capabilities"

2. Создание сессии. Используйте sessionScope: "thread", если вызывающие не должны разделять один разговор — значение по умолчанию "single" заставляет повторное создание в том же рабочем пространстве переиспользовать существующую сессию, сериализуя независимых вызывающих через одну очередь.

SESSION_JSON="$(curl -sX POST "$DAEMON_URL/session" \ -H @<(printf 'Authorization: Bearer %s\n' "$QWEN_SERVER_TOKEN") -H 'Content-Type: application/json' \ -d '{"sessionScope":"thread"}')" || echo "create failed (curl exit $?)" >&2 printf '%s\n' "$SESSION_JSON" SID="$(printf '%s' "$SESSION_JSON" | jq -er '.sessionId // empty')" export SID : "${SID:?no sessionId in the create response}" # → {"sessionId":"…","workspaceCwd":"/srv/project","attached":false}

3. Подписка до отправки промпта. Запустите это во втором терминале с тем же QWEN_SERVER_TOKEN и DAEMON_URL, и с SID, установленным в sessionId, который напечатал шаг 2: экспорты не пересекают терминалы, поэтому переэкспортируйте токен и DAEMON_URL там и установите SID в этот sessionId самостоятельно. Не вставляйте блок обратно в терминал 1 — его присваивание SID= перезапишет значение, которое используют шаги 4-6. Last-Event-ID: 0 воспроизводит с самого старого сохранённого события — так вы ловите события, возникшие между созданием и подпиской, в частности model_switch_failed. При подключении (значение по умолчанию sessionScope: "single" переиспользует существующую сессию) это событие — единственный сигнал того, что некорректный modelServiceId был отклонён, потому что ошибка намеренно не передаётся как HTTP-ошибка. При новом создании с modelServiceId — чего тело шага 2 не содержит — тело 200 также содержит modelApplied, false если переключение было отклонено, и это детерминированный сигнал для действия, в отличие от события на кольце с ограниченным буфером. Создание без modelServiceId вообще не имеет ключа modelApplied.

# терминал 2 — переэкспортируйте то, что нужно; переменные оболочки не пересекают терминалы # export QWEN_SERVER_TOKEN='<the token from step 1>' # export DAEMON_URL=http://127.0.0.1:4170 SID='<sessionId from step 2>' curl -N "$DAEMON_URL/session/$SID/events" \ -H @<(printf 'Authorization: Bearer %s\n' "$QWEN_SERVER_TOKEN") \ -H 'Accept: text/event-stream' -H 'Last-Event-ID: 0'

Каждая строка data: — это полный Envelope на одной строке; type Envelope совпадает со строкой event:.

Воспроизведение ограничено --event-ring-size и фиксированным байтовым бюджетом 8 МиБ на подписку. Если поток выдаёт state_resync_required с reason: "replay_budget_exceeded", восстановитесь через POST /session/:id/load, а не считайте воспроизведение завершённым.

4. Промптинг. 202 означает допуск, а не завершение. Соотнесите turn_complete / turn_error в потоке по promptId. Читайте stopReason в turn_complete; в turn_error читайте message и необязательные code / errorKind — см. POST /session/:id/prompt.

curl -sX POST "$DAEMON_URL/session/$SID/prompt" \ -H @<(printf 'Authorization: Bearer %s\n' "$QWEN_SERVER_TOKEN") -H 'Content-Type: application/json' \ -d '{"prompt":[{"type":"text","text":"What does src/main.ts do?"}]}' # → 202 {"promptId":"…","lastEventId":42}

5. Ответы на запросы разрешений. Когда агент хочет выполнить инструмент и его режим одобрения требует подтверждения, он выдаёт permission_request, и ход блокируется, пока кто-то не ответит или вы не отмените — по умолчанию таймаута нет (--permission-response-timeout-ms по умолчанию 0 = ждать бесконечно), поэтому неотвеченный запрос продолжает занимать слот в очереди промптов сессии, пока вы не отмените или не закроете сессию. Установите собственный дедлайн, если потоку он нужен.

Режим — это собственная настройка Qwen дочернего процесса tools.approvalMode, разрешаемая из настроек хоста демона и каталога --workspace; демон ничего не фиксирует при запуске. По умолчанию auto, который одобряет один класс вызовов инструментов без спрашивания — те не выдают permission_request вообще — и продолжает спрашивать для остальных. Недоверенная папка рабочего пространства принудительно переключается на default (спрашивать), поэтому одна инсталляция видит эти события, а другая нет, и GET /capabilities сообщает политику посредничества голосов, а не режим одобрения, так что предварительный запрос не скажет, в какой позиции вы находитесь. Если ваша интеграция зависит от гейтинга одобрения, зафиксируйте tools.approvalMode явно и решите заранее, как он отвечает: автоодобрение уже может действовать без того, чтобы кто-либо его выбирал.

Отвечайте на маршруте сессии: он достигает владеющего рабочего пространства, когда ровно одна активная среда выполнения владеет сессией, и никогда не возвращается к первичному бриджу. Недоверенный не-основной владелец возвращает 403 untrusted_workspace; основная среда выполнения освобождена от этой проверки доверия, поэтому недоверенный основной владелец может принять голос. Неразрешённый владелец завершается с fail closed вместо голосования на неправильной среде выполнения — 404 session_not_found, 500 ambiguous_session_owner или 503 workspace_runtime_unavailable с Retry-After: 1 (повторить; голос не был записан). Скопируйте data.requestId из события permission_request и установите его перед голосованием:

export REQUEST_ID='<data.requestId>' curl -sX POST "$DAEMON_URL/session/$SID/permission/$REQUEST_ID" \ -H @<(printf 'Authorization: Bearer %s\n' "$QWEN_SERVER_TOKEN") -H 'Content-Type: application/json' \ -d '{"outcome":{"outcome":"selected","optionId":"proceed_once"}}'

6. Закрытие. DELETE /session/$SID → 204. Сессия на диске сохраняется.

Эксплуатация

ЗаботаГде
Ограничения параллелизма--max-sessions, --max-total-sessions; создания сверх лимита возвращают 503 с Retry-After
Ограничение частоты--rate-limit и флаги --rate-limit-* для каждого класса
Очистка неактивных--session-idle-timeout-ms; поддержка активности через POST /session/:id/heartbeat
Память--child-heap-mode — только наблюдение. --memory-budget-mb управляет адаптивным пулом роста live-журнала для POST /session/:id/load, а не для воспроизведения SSE; фиксация --max-journal-bytes или --max-journal-events отключает рост. Ни один флаг не определяет размер дочерних процессов и не отказывает в запуске, и не управляет их фактическим потолком кучи (--max-old-space-size, производный от памяти хоста). См. Конфигурация для расчёта бюджета. Воспроизведение SSE отдельно ограничено --event-ring-size и фиксированным бюджетом 8 МиБ на подписку; пропущенный хвост выдаёт state_resync_required с reason: "replay_budget_exceeded"
Дедлайны промптов--prompt-deadline-ms; истечение выдаёт turn_error
ОшибкиКлассификация ошибок
НаблюдаемостьНаблюдаемость
Полный список флаговКонфигурация
Last updated on