Amiga
Оглавление курса · урок 11 из 14

Модуль 3 · Встраивание · урок 11 из 14

App Server: свой клиент для Codex

Все поверхности Codex — CLI, расширение для IDE, десктоп, веб — работают на одном и том же агентном цикле. Связующее звено между циклом и интерфейсом называется App Server. Если вы строите свой клиент — панель для команды, интеграцию во внутренний портал, оркестратор, который ведёт много агентов, — это ваш уровень. В уроке разберём протокол, напишем минимальный клиент и поймём, когда App Server действительно нужен, а когда хватит SDK.

Зачем отдельный протокол

В статье OpenAI о создании App Server (февраль 2026) рассказана история. Расширению VS Code нужен был тот же цикл, что в терминальном интерфейсе. Сначала пробовали выставить Codex как MCP-сервер, но удержать семантику MCP так, чтобы она подходила IDE, оказалось трудно. В итоге сделали JSON-RPC-протокол, повторяющий цикл TUI. Потом пришли партнёры — JetBrains и Xcode, — и десктопному приложению понадобилось вести много агентов параллельно. Отсюда требования стабильности и обратной совместимости.

Что даёт App Server по сравнению с SDK: авторизацию, историю разговоров, одобрения и потоковые события агента в одном протоколе. Документация формулирует прямо: если вы автоматизируете задачи или запускаете Codex в CI, берите SDK. App Server — для глубокой интеграции в свой продукт. Учтите риск: на октябрь 2026 документация называет саму команду codex app-server экспериментальной и не поддерживаемой для продакшен-нагрузок, а не только транспорт WebSocket.

Если вы читали старые инструкции. Команда codex mcp-server и отдельный бинарник codex-mcp-server удалены. Существующие интеграции документация предлагает переводить на App Server.

Транспорты

bash
codex app-server                                # stdio, по умолчанию
codex app-server --listen ws://127.0.0.1:4500   # WebSocket (экспериментально)
codex app-server --listen unix://               # Unix-сокет по умолчанию
  • stdio — JSON Lines: одно сообщение на строку. Самый простой и надёжный вариант для клиента, который сам запускает процесс.
  • WebSocket — одно сообщение JSON-RPC на текстовый кадр. Транспорт экспериментальный и официально не поддерживается для продакшена. Тот же слушатель отдаёт проверки GET /readyz и GET /healthz.
  • Unix-сокет — WebSocket поверх Unix-сокета.

Важное предупреждение про WebSocket. Слушатель не на loopback-адресе сейчас по умолчанию принимает соединения без аутентификации. Прежде чем открывать его наружу, настройте авторизацию (--ws-auth capability-token --ws-token-file /абсолютный/путь или подписанные bearer-токены) и TLS. Простой ws:// годится только для localhost и SSH-туннеля.

Той же механикой пользуется удалённый терминальный режим: codex app-server --listen ws://127.0.0.1:4500 на одной машине и codex --remote ws://127.0.0.1:4500 на другой.

Протокол

App Server говорит на JSON-RPC 2.0, но без поля "jsonrpc": "2.0" в сообщениях. Три вида сообщений:

json
{ "method": "thread/start", "id": 10, "params": { "model": "gpt-6.1-sol" } }
{ "id": 10, "result": { "thread": { "id": "thr_123" } } }
{ "method": "turn/started", "params": { "turn": { "id": "turn_456" } } }

Запрос несёт method, params и id. Ответ повторяет id и содержит result или error. Уведомление идёт без id.

Протокол двунаправленный: сервер тоже отправляет запросы клиенту. Так приходят одобрения — сервер ставит ход на паузу и ждёт решения.

Примитивы

  • Thread — разговор пользователя с агентом, долговечный контейнер. Потоки создают, продолжают, ветвят, архивируют.
  • Turn — один запрос пользователя и вся работа агента после него.
  • Item — единица ввода или вывода: сообщение пользователя, сообщение агента, выполнение команды, изменение файла, вызов инструмента. Жизненный цикл: item/started → необязательные дельты → item/completed. Финальным состоянием считается item/completed.

Жизненный цикл соединения

  1. Клиент отправляет initialize с clientInfo и затем уведомление initialized. До этого сервер отвергает любые запросы, повторный initialize возвращает ошибку.
  2. thread/start создаёт поток, thread/resume продолжает, thread/fork ветвит.
  3. turn/start с threadId и вводом пользователя начинает ход. turn/steer добавляет указание в идущий ход, turn/interrupt прерывает.
  4. Клиент читает уведомления: item/started, item/agentMessage/delta, item/completed, turn/diff/updated, turn/plan/updated.
  5. turn/completed сообщает статус: completed, interrupted или failed.

Минимальный клиент

Клиент на Node.js: запускает сервер по stdio, создаёт поток, отправляет задачу, печатает ответ агента по мере генерации и отклоняет все запросы на одобрение.

typescript · client.mts
import { spawn } from "node:child_process";
import readline from "node:readline";

const proc = spawn("codex", ["app-server"], { stdio: ["pipe", "pipe", "inherit"] });
const rl = readline.createInterface({ input: proc.stdout });
const send = (msg: unknown) => proc.stdin.write(JSON.stringify(msg) + "\n");

rl.on("line", (line) => {
  const msg = JSON.parse(line);

  // ответ на thread/start — запускаем ход
  if (msg.id === 1 && msg.result?.thread?.id) {
    send({
      method: "turn/start",
      id: 2,
      params: {
        threadId: msg.result.thread.id,
        input: [{ type: "text", text: "Кратко опиши структуру этого репозитория." }],
      },
    });
  }

  // запрос от сервера: одобрение команды или правки файла
  const approvals = ["item/commandExecution/requestApproval", "item/fileChange/requestApproval"];
  if (msg.id !== undefined && approvals.includes(msg.method)) {
    console.error(`\n[отклонено] ${msg.params?.command ?? msg.method}`);
    send({ id: msg.id, result: { decision: "decline" } });
  }

  if (msg.method === "item/agentMessage/delta") process.stdout.write(msg.params.delta);

  if (msg.method === "turn/completed") {
    console.log(`\n[ход завершён: ${msg.params.turn.status}]`);
    proc.kill();
  }
});

send({ method: "initialize", id: 0, params: { clientInfo: { name: "amiga_panel", title: "Amiga Panel", version: "0.1.0" } } });
send({ method: "initialized", params: {} });
send({ method: "thread/start", id: 1, params: { cwd: process.cwd(), approvalPolicy: "on-request", sandbox: "read-only" } });

Запуск: npx tsx client.mts внутри любого репозитория.

Что здесь стоит заметить:

  • Одобрения. Запросы item/commandExecution/requestApproval и item/fileChange/requestApproval приходят с id, и на них нужно ответить решением. Для команд доступны accept, acceptForSession, decline, cancel и вариант с поправкой правил выполнения. После ответа сервер присылает serverRequest/resolved, а затем item/completed со статусом completed, failed или declined. Настоящий клиент показывает пользователю команду, рабочий каталог и причину запроса. Наш просто отказывает. Другие запросы от сервера — item/permissions/requestApproval, item/tool/requestUserInput, mcpServer/elicitation/request — отвечаются в своём формате, их клиент тоже должен обрабатывать, иначе ход будет ждать ответа.
  • clientInfo.name. Это не формальность: по нему клиент идентифицируется в журналах соответствия OpenAI (Compliance Logs). Если вы делаете интеграцию для корпоративного использования, документация просит связаться с OpenAI, чтобы её добавили в список известных клиентов.
  • Значения полей. В примерах документации и в сгенерированной схеме встречаются разные написания значений, например режима песочницы. Не угадывайте — генерируйте схему под свою версию.

Схема под вашу версию

Протокол развивается, поэтому типы не надо писать руками:

bash
codex app-server generate-ts --out ./schemas
codex app-server generate-json-schema --out ./schemas

Вывод соответствует ровно той версии Codex, которой вы его сгенерировали. Закрепите версию CLI в своём продукте и перегенерируйте схему при обновлении. Так поступают и официальные клиенты: локальные приложения поставляют бинарник App Server, закреплённый на проверенной версии.

Часть методов и полей скрыта за флагом capabilities.experimentalApi в initialize. Без него сервер отвергает экспериментальные вызовы — и это хорошо: стабильная поверхность не сломается при обновлении. Для быстрой проверки без клиента есть отладочная команда codex debug app-server send-message-v2 "...".

Что ещё умеет протокол

Кроме потоков и ходов, в App Server есть ревью (review/start с целями uncommittedChanges, baseBranch, commit, custom), список моделей (model/list), навыки, приложения-коннекторы, авторизация через API-ключ или ChatGPT, лимиты и расход токенов. Сервер присылает и ошибки с типами — например, ContextWindowExceeded или UsageLimitExceeded, — чтобы клиент мог показать понятное сообщение. Полный список — в документации App Server. Исходники лежат в репозитории openai/codex.

Когда это оправдано

App Server стоит своих затрат, если вам нужно хотя бы одно из трёх:

  • Свой интерфейс с одобрениями. Человек должен видеть, что агент собирается сделать, и отвечать из вашего UI.
  • Много агентов под управлением программы. Оркестратор, который ведёт задачи из трекера, перезапускает зависших агентов и собирает результаты. Открытая спецификация OpenAI Symphony описывает именно такую схему поверх codex app-server — подробнее в уроке «Оркестрация параллельных агентов».
  • Удалённый агент. Агент работает рядом с кодом и вычислениями, а клиент подключается к нему по сети.

Во всех остальных случаях начните с SDK. Python SDK уже работает поверх app-server, и вы получите большую часть возможностей без собственной обвязки.

Попробуйте сами

10–15 мин на рабочем месте
  1. Запустите codex app-server generate-ts --out ./schemas и найдите в схеме типы ThreadStartParams и CommandExecutionRequestApprovalResponse.
  2. Сохраните клиент из урока и запустите его. Затем попросите агента «создай файл hello.txt» и посмотрите, как приходит запрос на одобрение и как сервер реагирует на отказ.
  3. Замените автоматический отказ на вопрос в консоли: показать команду и спросить y/n.

Коротко

  • App Server — двунаправленный JSON-RPC (без поля jsonrpc), на нём работают официальные клиенты Codex.
  • Транспорт по умолчанию — stdio с JSONL. WebSocket экспериментальный, без авторизации наружу не открывать.
  • Рукопожатие initialize → initialized, дальше потоки, ходы и элементы с потоковыми событиями.
  • Одобрения — запросы от сервера, клиент отвечает решением.
  • Схему генерируйте под свою версию CLI. Для CI и простой автоматизации берите SDK.
Отметка хранится только в вашем браузере
Хотите связаться с владельцем
компании напрямую?
Дмитрий Тарасов
Дмитрий Тарасов
СЕО

НАПИСАТЬ

Написать в MAX