Модуль 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 или новее. Библиотеку используют на сервере:
npm install @openai/codex-sdkБазовая модель: Codex → поток (Thread) → ходы (run). Каждый вызов run() на том же потоке продолжает разговор.
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("Реализуй исправление");Настройки потока задаются при создании:
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.
Рабочий пример: отчёт о риске изменений
Скрипт проверяет ветку, получает ответ по схеме, ограничивает время работы и сохраняет идентификатор потока для продолжения.
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():
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 потерян — например, процесс перезапустился, — его можно восстановить по идентификатору:
const thread = codex.resumeThread(savedThreadId);
await thread.run("Продолжи с того места, где остановился");Окружение и конфигурация
По умолчанию CLI наследует переменные окружения процесса Node.js. Параметр env в конструкторе Codex задаёт их полностью. Это полезно, если в окружении вашего сервиса лежат секреты, которые агенту видеть незачем. SDK при этом сам добавит то, что ему нужно, например CODEX_API_KEY.
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:
pip install openai-codexТа же модель — потоки и ходы, но с контекстным менеджером:
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 есть отдельный тип для содержимого, которое пришло не от пользователя: текст задачи из трекера, уведомление из канала, вывод другого агента.
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 мин на рабочем месте- Установите
@openai/codex-sdk, сохраните примерrisk-report.mtsи запустите его на своём репозитории. Посмотрите, что лежит вturn.items. - Перепишите пример на
runStreamed()и выведите в консоль каждую выполненную команду с кодом выхода. - На 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.