pimenov.ai

База знаний

codex app-server — как встроить Codex в собственное приложение

codex app-server — JSON-RPC интерфейс Codex для IDE и своих клиентов: транспорты, потоки и ходы, подтверждения действий, удалённый доступ и безопасность.

Опубликовано

Практическое руководство по codex app-server: двунаправленному JSON-RPC интерфейсу, через который расширение Codex для VS Code и другие клиенты управляют агентом. Разберём запуск и транспорты, инициализацию, работу с потоками и ходами, подтверждения действий и безопасный удалённый доступ.

🔄
Актуальность: проверено 24 августа 2026 года по официальной документации OpenAI и README app-server в репозитории openai/codex. Часть API экспериментальная и меняется между версиями: перед внедрением сгенерируйте схему из своей установленной версии CLI (команды в разделе про протокол).
📌
Коротко: app-server нужен, когда вы встраиваете Codex в собственный продукт и вам требуются история диалогов, потоковые события и подтверждения действий. Для автоматизации и CI берите Codex SDK, для разовых неинтерактивных запусков из терминала — codex exec.

Содержание

  1. Что такое codex app-server
  2. Что выбрать: SDK, exec, MCP или app-server
  3. Запуск и транспорты
  4. Протокол и схема сообщений
  5. Жизненный цикл: инициализация, потоки, ходы
  6. Карта методов
  7. События и ошибки
  8. Подтверждения и sandbox
  9. Удалённый доступ и авторизация
  10. Полезные сценарии
  11. Проверка результата
  12. Ограничения и когда не подходит
  13. Официальные источники

Что такое codex app-server

codex app-server — подкоманда Codex CLI, которая поднимает сервер с двунаправленным протоколом JSON-RPC 2.0 (формат удалённого вызова процедур поверх JSON). Через него клиент получает возможности полноценного интерфейса Codex: аутентификацию, историю диалогов, потоковые события агента, выполнение команд в sandbox и подтверждения действий.

Именно этот протокол использует расширение Codex для VS Code, поэтому app-server подходит для собственных IDE-интеграций, веб-приложений и десктопных клиентов. Реализация открыта: каталог codex-rs/app-server в репозитории openai/codex.

Язык клиента не ограничен: достаточно читать и писать по одному JSON-сообщению в строке. Подойдут TypeScript, Python, Go, Rust и другие языки.

Что выбрать: SDK, exec, MCP или app-server

ИнструментЗадачаПричина выбора
Codex SDKАвтоматизация задач, CI, скриптыПроще и стабильнее, без ручной работы с протоколом
codex execРазовый неинтерактивный запуск из терминалаНе нужен сервер и постоянное соединение
Codex как MCP serverОтдать инструменты Codex другому агентуСтандартный протокол MCP
codex app-serverСвой клиент с историей, стримингом и подтверждениямиПолный доступ к harness Codex: тот же протокол, что у VS Code extension

Запуск и транспорты

npm install -g @openai/codex

# stdio (по умолчанию): одна строка = одно JSON-сообщение
codex app-server

# WebSocket на loopback-интерфейсе
codex app-server --listen ws://127.0.0.1:4500

# Unix-сокет по умолчанию
codex app-server --listen unix://
ТранспортФлагФормат и статус
stdio--listen stdio://JSONL по stdin/stdout; режим по умолчанию для дочерних процессов
WebSocket--listen ws://IP:PORTОдно сообщение на текстовый фрейм; experimental, для production не поддерживается
Unix socket--listen unix:// или unix://PATHWebSocket-подключение через сокет с HTTP Upgrade
off--listen offЛокальный транспорт не открывается

WebSocket-листенер дополнительно отвечает на HTTP-проверки: GET /readyz возвращает 200 OK, когда сервер принимает соединения; GET /healthz возвращает 200 OK для запросов без заголовка Origin, а запросы с Origin отклоняются с 403 Forbidden.

Протокол и схема сообщений

Как и MCP, app-server работает поверх JSON-RPC 2.0, но заголовок "jsonrpc": "2.0" на проводе опускается.

Запрос содержит method, params и id:

{ "method": "thread/start", "id": 10, "params": { "model": "gpt-5.4" } }

Ответ повторяет id и содержит result или error:

{ "id": 10, "result": { "thread": { "id": "thr_123" } } }

Уведомление идёт без id:

{ "method": "turn/started", "params": { "turn": { "id": "turn_456" } } }

Схему сообщений не нужно переписывать вручную: CLI генерирует TypeScript-типы или JSON Schema из вашей установленной версии, и артефакты гарантированно ей соответствуют.

codex app-server generate-ts --out ./schemas
codex app-server generate-json-schema --out ./schemas
💡
Совет: перегенерируйте схему после каждого обновления Codex CLI. Проверить доступные модели и их параметры можно вызовом model/list, не зашивая конкретные идентификаторы в код клиента.

Жизненный цикл: инициализация, потоки, ходы

Протокол строится на трёх примитивах:

  • поток (thread) — диалог пользователя с агентом, состоит из ходов;
  • ход (turn) — один запрос пользователя и работа агента по нему;
  • элемент (item) — единица ввода или вывода: сообщение, запуск команды, правка файла, вызов инструмента.

Порядок работы соединения:

  1. Клиент отправляет initialize с clientInfo (имя, заголовок, версия клиента) и ждёт ответ.
  2. Клиент отправляет уведомление initialized. До этого рукопожатия сервер отвечает ошибкой Not initialized, повторный initialize на том же соединении даёт Already initialized.
  3. Клиент открывает диалог: thread/start для нового, thread/resume для продолжения по сохранённому id, thread/fork для ветвления истории.
  4. Клиент запускает ход: turn/start с threadId и входными элементами (текст, изображение по URL или локальный файл).
  5. Во время хода клиент читает поток уведомлений: item/started, дельты текста, item/completed. Добавить реплику в активный ход можно через turn/steer, остановить — через turn/interrupt.
  6. Ход завершается уведомлением turn/completed со статусом completed, interrupted или failed.

В initialize.params.capabilities клиент может подписаться на экспериментальные методы (experimentalApi: true) и отключить ненужные уведомления по точным именам (optOutNotificationMethods).

⚖️
Нюанс: без experimentalApi сервер отклоняет экспериментальные методы и поля ошибкой вида <descriptor> requires experimentalApi capability. Опт-ин фиксируется один раз на всё время жизни соединения.

Карта методов

ГруппаОсновные методы
Потокиthread/start, thread/resume, thread/fork, thread/read, thread/list, thread/archive, thread/unarchive, thread/delete, thread/compact/start, thread/goal/set
Ходыturn/start, turn/steer, turn/interrupt, review/start
Команды и процессыcommand/exec с write/resize/terminate; process/* — experimental, вне sandbox
Модели и флагиmodel/list, experimentalFeature/list, permissionProfile/list
Конфигурацияconfig/read, config/value/write, config/batchWrite, configRequirements/read
Файлыfs/readFile, fs/writeFile, fs/readDirectory, fs/watch и другие методы fs/*
Навыки и плагиныskills/list, skills/config/write, marketplace/*; plugin/list, plugin/install и соседние пока в разработке
Приложения (коннекторы)app/list, вызов через маркер $demo-app в тексте ввода и элемент mention с путём app://demo-app
Аккаунтaccount/read, account/login/start (API key, ChatGPT browser flow, device code), account/logout, account/rateLimits/read, account/usage/read

События и ошибки

После старта или возобновления потока клиент постоянно читает уведомления транспорта. Ключевые:

  • thread/started, thread/archived, thread/closed, thread/status/changed — жизненный цикл диалога;
  • turn/started, turn/completed, turn/diff/updated, turn/plan/updated — ход работы;
  • item/started и item/completed — начало и финал каждого элемента; финальное состояние элемента берите из item/completed;
  • item/agentMessage/delta, item/reasoning/summaryTextDelta, item/commandExecution/outputDelta — потоковые дельты текста, рассуждений и вывода команд;
  • thread/tokenUsage/updated — расход токенов по активному потоку.

При сбое хода сервер отправляет error с полем codexErrorInfo и завершает ход статусом failed. Типичные значения: ContextWindowExceeded, UsageLimitExceeded, HttpConnectionFailed, ResponseStreamDisconnected, SandboxError. Если доступен HTTP-статус от вышестоящего сервиса, он приходит в httpStatusCode.

Отдельный случай — перегрузка. При заполнении входящей очереди сервер отклоняет новые запросы кодом -32001 и сообщением Server overloaded; retry later. Клиенту следует повторять запрос с экспоненциальной задержкой и джиттером.

Подтверждения и sandbox

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

Порядок сообщений для команды:

  1. item/started с элементом commandExecution.
  2. Запрос item/commandExecution/requestApproval с itemId, threadId, turnId и деталями команды.
  3. Ответ клиента: accept, acceptForSession, decline или cancel; для команд есть ещё вариант acceptWithExecpolicyAmendment: принять команду и заодно дописать правило в политику execpolicy.
  4. serverRequest/resolved фиксирует, что запрос закрыт.
  5. item/completed с финальным статусом completed, failed или declined.

Для правок файлов схема та же: item/fileChange/requestApproval и те же решения. Если в запросе есть networkApprovalContext, это запрос сетевого доступа к конкретному хосту и протоколу: показывайте сетевой диалог, а не превью shell-команды.

Политика изоляции задаётся в sandboxPolicy при старте потока или хода: readOnly, workspaceWrite с явными writableRoots, externalSandbox (если изоляцию уже обеспечивает ваш контур) или dangerFullAccess. Политика подтверждений — в approvalPolicy.

🔴
Критично: метод thread/shellCommand выполняется вне sandbox с полным доступом и не наследует политику потока. Показывайте его в клиенте только для явно инициированных пользователем команд. То же относится к экспериментальному process/*.

Встроенный инструмент request_permissions приходит запросом item/permissions/requestApproval. В ответе передавайте только запрошенное подмножество прав и явно выбирайте область действия: scope: "turn" на ход или "session" на сессию.

Удалённый доступ и авторизация

App-server можно запустить на одной машине, а терминальный интерфейс Codex подключить с другой:

# на хосте
codex app-server --listen ws://127.0.0.1:4500

# на клиенте
codex --remote ws://127.0.0.1:4500

Опция --remote принимает endpoint ws://, wss:// и unix://. Для небезопасного канала держите листенер на loopback или пробрасывайте порт по SSH. Для удалённого соединения включите авторизацию и TLS, а токен передавайте через переменную окружения:

export CODEX_REMOTE_TOKEN="$(cat "$HOME/.codex/app-server-token")"
codex --remote wss://remote-host:4500 \
  --remote-auth-token-env CODEX_REMOTE_TOKEN
⚠️
Внимание: на период постепенного rollout нелокальные WebSocket-листенеры по умолчанию принимают соединения без авторизации. Перед тем как открыть порт наружу, настройте --ws-auth: capability-token с --ws-token-file или --ws-token-sha256, либо signed-bearer-token с --ws-shared-secret-file. Не передавайте сырой токен аргументом командной строки.

Полезные сценарии

«Спросить Codex» прямо внутри своего продукта

Задача: у команды есть свой рабочий инструмент (админка, редактор, внутренний портал), и хочется, чтобы сотрудник мог задать вопрос Codex, не выходя из него: с историей диалога, живой печатью ответа и кнопками «разрешить/отклонить» для действий агента.

Условия: установленный Codex CLI на машине, где работает сервис, и немного кода на любом языке: протоколу достаточно чтения и записи JSON построчно.

Что делать: сервис запускает codex app-server как дочерний процесс, проходит рукопожатие initialize и initialized, открывает поток через thread/start и отправляет вопрос пользователя через turn/start. Ответ агента приходит дельтами item/agentMessage/delta и сразу показывается в интерфейсе. Когда агент просит разрешения на команду или правку, интерфейс получает обычный запрос и отвечает решением пользователя.

Минимальный клиент на Node.js выглядит так:

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 = (message: unknown) => {
  proc.stdin.write(`${JSON.stringify(message)}\n`);
};

let threadId: string | null = null;
rl.on("line", (line) => {
  const msg = JSON.parse(line);
  if (msg.id === 1 && msg.result?.thread?.id && !threadId) {
    threadId = msg.result.thread.id;
    send({
      method: "turn/start",
      id: 2,
      params: {
        threadId,
        input: [{ type: "text", text: "Summarize this repo." }],
      },
    });
  }
});

send({
  method: "initialize",
  id: 0,
  params: {
    clientInfo: { name: "my_product", title: "My Product", version: "0.1.0" },
  },
});
send({ method: "initialized", params: {} });
send({ method: "thread/start", id: 1, params: { model: "gpt-5.4" } });

Наблюдаемый результат: пользователь видит, как ответ печатается по мере генерации; финал — уведомление turn/completed со статусом completed, а собранный текст берётся из item/completed.

Ограничение: один initialize на соединение; экспериментальные методы требуют capabilities.experimentalApi при инициализации.

Codex остаётся дома, вы работаете с ноутбука

Задача: проекты и настроенный Codex живут на стационарном компьютере, например на домашнем Mac mini, а вы уехали с ноутбуком и хотите продолжить работу в привычном терминальном интерфейсе.

Что делать: на домашней машине запустите codex app-server --listen ws://127.0.0.1:4500 и пробросьте порт по SSH. На ноутбуке подключитесь командой codex --remote ws://127.0.0.1:4500. Токен храните в файле и передавайте через переменную окружения, как показано в разделе про удалённый доступ.

Наблюдаемый результат: терминал на ноутбуке выглядит как обычный Codex, но команды выполняются на домашней машине; curl -sf http://127.0.0.1:4500/readyz отвечает 200 OK.

Ограничение: WebSocket-транспорт экспериментальный; без --ws-auth и TLS такой доступ наружу не выставляют.

Обновление Codex CLI без сюрпризов

Задача: вы обновили Codex CLI и хотите заранее узнать, не сломала ли новая версия вашу интеграцию.

Что делать: после каждого обновления выполните codex app-server generate-ts --out ./schemas (или generate-json-schema для других языков) и соберите клиент заново.

Наблюдаемый результат: все расхождения между клиентом и новой версией протокола всплывают сразу на этапе сборки, до выкладки.

Ограничение: схема соответствует только той версии CLI, из которой её сгенерировали, поэтому сгенерированные файлы логично хранить рядом с кодом клиента.

Проверка результата

Минимальный рабочий сценарий для проверки интеграции:

  1. Запустите codex app-server --listen ws://127.0.0.1:4500.
  2. Выполните curl -sf http://127.0.0.1:4500/readyz: ответ 200 OK подтверждает, что листенер принимает соединения.
  3. Отправьте initialize, дождитесь ответа с user-agent сервера, отправьте initialized.
  4. Выполните thread/start, затем turn/start с простым текстовым запросом.
  5. Признак успеха: поток item/* уведомлений и финальный turn/completed со статусом completed. Ошибка Not initialized означает нарушенный порядок рукопожатия.

Ограничения и когда не подходит

  • Команда app-server и WebSocket-транспорт официально экспериментальные и не поддерживаются для production-нагрузок.
  • Для CI и прямой автоматизации документация рекомендует Codex SDK: протокол app-server шире и нужен именно для богатых клиентских интеграций.
  • Методы plugin/list, plugin/read, plugin/install и plugin/uninstall помечены как находящиеся в разработке; из production-клиентов их вызывать не стоит.
  • thread/rollback и уведомление item/fileChange/outputDelta объявлены устаревшими; для правок используйте элементы fileChange и turn/diff/updated.
  • При перегрузке ждите -32001 и повторяйте запросы с экспоненциальной задержкой и джиттером.

Это руководство основано на официальной документации OpenAI и README app-server, а не на прогоне каждого метода локально. Поведение экспериментальных флагов сверяйте со своей версией CLI.

Официальные источники

Следующий шаг

Если ваша задача — не собственный клиент, а запуск задач Codex из терминала и скриптов, продолжите здесь:

codex exec — как выполнять конкретные задачи Codex из терминала и скриптов

Связанные материалы

App-server открывает дорогу к собственным интерфейсам и рабочим контурам вокруг Codex. Если вы встраиваете агента в продукт или командный процесс, архитектуру интеграции полезно обсудить до того, как протокол зафиксируется в коде.

Если захотите обсудить, как это применить у себя или в команде — пишите в Telegram @pimenov