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

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

Codex SDK: TypeScript и Python

codex exec хорош в shell-скриптах. Но когда Codex становится частью вашего сервиса — бота для трекера, внутреннего инструмента, ночного обработчика очереди задач, — удобнее библиотека с нормальными типами, исключениями и асинхронностью. Для этого есть Codex SDK на TypeScript и Python. В уроке — как они устроены, чем отличаются и как написать на них небольшой рабочий инструмент.

Когда SDK, а когда что-то другое

Нужно Инструмент
Один запуск в CI, вывод в файл codex exec
Codex внутри своей программы: несколько ходов, разбор результата в коде SDK
Свой интерфейс с потоком событий, одобрениями и историей App Server

Документация так и разделяет: SDK — для автоматизации и задач в CI, App Server — для своих клиентов. Обе библиотеки управляют локальным Codex: агент работает на той машине, где запущена ваша программа, в её файловой системе и с её песочницей.

Устроены они по-разному. TypeScript SDK запускает CLI codex и обменивается с ним событиями JSONL — по сути это типизированная обёртка над codex exec --json. Python SDK управляет локальным app-server по JSON-RPC, а нужная версия CLI поставляется вместе с пакетом.

TypeScript

Установка, нужен Node.js 18 или новее. Библиотеку используют на сервере:

bash
npm install @openai/codex-sdk

Базовая модель: Codex → поток (Thread) → ходы (run). Каждый вызов run() на том же потоке продолжает разговор.

typescript
import { Codex } from "@openai/codex-sdk";

const codex = new Codex();
const thread = codex.startThread();
const turn = await thread.run("Найди причину падения теста и предложи исправление");

console.log(turn.finalResponse);
console.log(turn.items); // команды, правки файлов, сообщения агента

const next = await thread.run("Реализуй исправление");

Настройки потока задаются при создании:

typescript
const thread = codex.startThread({
  workingDirectory: "/srv/checkouts/site",
  sandboxMode: "read-only",       // read-only | workspace-write | danger-full-access
  approvalPolicy: "never",        // в автоматизации спрашивать некого
  model: "gpt-6.1-sol",           // название на октябрь 2026; проверьте доступность
  modelReasoningEffort: "medium",
  networkAccessEnabled: false,
  skipGitRepoCheck: false,
});

В типах SDK для approvalPolicy ещё перечислены untrusted и on-failure, но в CLI первое больше не поддерживается, а второе устарело. Используйте never или on-request.

Рабочий пример: отчёт о риске изменений

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

typescript · risk-report.mts
import { Codex } from "@openai/codex-sdk";

const schema = {
  type: "object",
  properties: {
    risk: { type: "string", enum: ["low", "medium", "high"] },
    summary: { type: "string" },
    files: { type: "array", items: { type: "string" } },
  },
  required: ["risk", "summary", "files"],
  additionalProperties: false,
} as const;

const codex = new Codex({ apiKey: process.env.CODEX_API_KEY });
const thread = codex.startThread({
  workingDirectory: process.argv[2] ?? process.cwd(),
  sandboxMode: "read-only",
  approvalPolicy: "never",
});

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 15 * 60 * 1000);

try {
  const turn = await thread.run(
    "Оцени риск изменений текущей ветки относительно origin/main. " +
      "Платежи, авторизация и миграции — не ниже medium.",
    { outputSchema: schema, signal: controller.signal },
  );
  const report = JSON.parse(turn.finalResponse);
  console.log(JSON.stringify({ threadId: thread.id, ...report }, null, 2));
  process.exitCode = report.risk === "high" ? 2 : 0;
} finally {
  clearTimeout(timer);
}

Запуск: npx tsx risk-report.mts /path/to/repo. Если ход завершился ошибкой, run() бросает исключение — обрабатывайте его как сбой, а не как «риск низкий». Схему можно собирать и из Zod через zod-to-json-schema с target: "openAi", пример есть в README TypeScript SDK.

Потоковые события и продолжение

run() ждёт конца хода. Чтобы реагировать на ход работы — писать в лог каждую команду, показывать прогресс, — используйте runStreamed():

typescript
const { events } = await thread.runStreamed("Прогони тесты и разбери падения");
for await (const event of events) {
  if (event.type === "item.completed" && event.item.type === "command_execution") {
    console.log(`[${event.item.exit_code}] ${event.item.command}`);
  }
  if (event.type === "turn.completed") console.log("tokens:", event.usage);
}

Потоки сохраняются на диск, и если объект Thread потерян — например, процесс перезапустился, — его можно восстановить по идентификатору:

typescript
const thread = codex.resumeThread(savedThreadId);
await thread.run("Продолжи с того места, где остановился");

Окружение и конфигурация

По умолчанию CLI наследует переменные окружения процесса Node.js. Параметр env в конструкторе Codex задаёт их полностью. Это полезно, если в окружении вашего сервиса лежат секреты, которые агенту видеть незачем. SDK при этом сам добавит то, что ему нужно, например CODEX_API_KEY.

typescript
const codex = new Codex({
  env: { PATH: "/usr/local/bin:/usr/bin:/bin", HOME: "/home/codex" },
  config: { shell_environment_policy: { inherit: "core" } },
});

config превращается в набор флагов --config key=value. Для ключей, которые не выражаются через точку, есть configOverrides — сырые строки TOML. Файлы конфигурации на диске эти параметры не меняют.

Python

Python 3.10 или новее. Пакет стабильный, а предварительные сборки ставятся с флагом --pre:

bash
pip install openai-codex

Та же модель — потоки и ходы, но с контекстным менеджером:

python
from openai_codex import ApprovalMode, Codex, Sandbox

schema = {
    "type": "object",
    "properties": {
        "risk": {"type": "string", "enum": ["low", "medium", "high"]},
        "summary": {"type": "string"},
    },
    "required": ["risk", "summary"],
    "additionalProperties": False,
}

with Codex() as codex:
    thread = codex.thread_start(
        cwd="/srv/checkouts/site",
        sandbox=Sandbox.read_only,
        approval_mode=ApprovalMode.deny_all,
    )
    result = thread.run("Оцени риск изменений относительно origin/main", output_schema=schema)
    print(result.status, result.final_response)
    print(result.usage)

Детали, которые легко пропустить:

  • Одобрения по умолчанию. thread_start() без параметра использует ApprovalMode.auto_review: запросы на выход за песочницу рассматривает агент-ревьюер. Для полностью неинтерактивной работы без эскалаций укажите ApprovalMode.deny_all.
  • Песочница на ход. Пресеты Sandbox.read_only, Sandbox.workspace_write, Sandbox.full_access. Песочница, переданная в run(), действует на этот ход и все следующие в потоке. Удобно: сначала правка с записью, потом ревью только на чтение.
  • final_response может быть None, если ход закончился без итогового сообщения. Проверяйте result.status и result.error.
  • Асинхронность. AsyncCodex с тем же набором методов. Для тонкого управления — thread.turn(...): у него есть stream(), steer() (добавить указание в идущий ход) и interrupt().
  • Продолжение и ветвление — codex.thread_resume(thread_id) и codex.thread_fork(thread_id).

Недоверенный ввод: ExternalMessage

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

python
from openai_codex import ExternalMessage

message = ExternalMessage(
    tool_name="tracker",
    namespace="kaiten",
    content=task_description,  # текст задачи, написанный кем угодно
)
result = thread.run(message)

Такое сообщение попадает к модели с полномочиями инструмента — ниже инструкций пользователя и разработчика. Оно не считается разрешением от пользователя. Это не защита от prompt injection, а правильная маркировка: модель знает, что текст чужой. Права песочницы всё равно должны соответствовать тому, что вы разрешили. Подробнее о защите — в уроке «Безопасность автоматизации».

Авторизация

TypeScript SDK принимает apiKey в конструкторе или берёт CODEX_API_KEY из окружения. Python SDK по умолчанию использует уже сохранённую авторизацию Codex, а явно войти можно через codex.login_api_key(...) или вход через ChatGPT, в том числе по device code. Для серверных сервисов выбирайте API-ключ — по тем же причинам, что и в CI.

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

10–15 мин на рабочем месте
  1. Установите @openai/codex-sdk, сохраните пример risk-report.mts и запустите его на своём репозитории. Посмотрите, что лежит в turn.items.
  2. Перепишите пример на runStreamed() и выведите в консоль каждую выполненную команду с кодом выхода.
  3. На Python запустите поток с Sandbox.workspace_write, попросите мелкую правку, затем вторым ходом с sandbox=Sandbox.read_only попросите проверить дифф.

Коротко

  • SDK — для Codex внутри вашей программы. Для разового запуска хватит codex exec, для своего интерфейса нужен App Server.
  • TypeScript SDK оборачивает CLI и события JSONL, Python SDK работает через app-server.
  • Модель общая: Codex → поток → ходы. Есть ответ по схеме, потоковые события, продолжение по идентификатору.
  • В Python по умолчанию auto_review. Для работы без эскалаций — ApprovalMode.deny_all.
  • Ошибка хода — исключение или статус, а не пустой результат. Чужой текст передавайте как ExternalMessage.
Отметка хранится только в вашем браузере
Хотите связаться с владельцем
компании напрямую?
Дмитрий Тарасов
Дмитрий Тарасов
СЕО

НАПИСАТЬ

Написать в MAX