Привет, Хабр
После знакомства с Codex CLI от OpenAI я решил провести практический тест: можно ли в российском ИИ-ландшафте собрать ChatGPT-подобный login UX для агентного CLI — запускаю клиент, логинюсь, сразу работаю с инференсом.
Сначала разберу рынок и авторизацию: как я сравнил Яндекс и Сбер, и почему для нужного UX Яндекс оказался проще в реализации. А потом покажу самое вкусное: что пришлось чинить в runtime inference, чтобы агент вообще не умирал на первом ходе.
TL;DR
-
Для agentic CLI необходим воспроизводимый мост
IDP login -> API token -> inference. -
У Яндекса такой мост в публичной документации есть:
OAuth -> IAM -> AI Studio API. -
У Сбера в персональном сценарии (
GIGACHAT_API_PERS) доступ к GigaChat API идёт черезClient ID/Client Secret -> /api/v2/oauth, то есть через app-credentials контур. -
Для гипотезы “сделать ChatGPT-like login UX в Codex CLI под российский инференс” путь через Яндекс оказался единственным вариантом, где связка
identity → computeсобирается без архитектурного разрыва. -
Бонус: Яндекс уже работает через
Responses API, поэтому не пришлось насильно возвращать Codex в легасиchat/completions. -
Самая жесть была не в авторизации, а в рантайме:
content_filter, устаревший формат tool-call сообщений и необходимость жёсткой нормализации контекста.
Какой UX я считал целевым
Мне нужен был простой критерий: после пользовательского логина CLI должен сразу получать рабочий compute-доступ от имени этого пользователя.
Без ручной проклейки ключей, без отдельного контура app-credentials, без разрыва между логином и инференсом.
Сравнение провайдеров по доступу к AI Compute API
Yandex: связка пользовательского входа и AI API реально собирается
По документации Yandex Cloud:
-
для AI Studio API нужен IAM token в
Authorization: Bearer; -
OAuth-токен пользователя можно обменять на IAM token.
Практически это даёт воспроизводимую цепочку:
-
Пользователь проходит OAuth-логин.
-
CLI получает OAuth token со scope
cloud:auth. -
CLI меняет его на IAM token.
-
CLI работает с AI Studio API в нужном облачном folder context.
Требования тут понятные и явно описаны в документации:
-
нужно зарегистрировать приложение в Yandex ID;
-
указать целевой scope
cloud:auth; -
у пользователя должен быть доступ к облачному аккаунту (включая ролевой доступ и биллинг).
Но ключевое для меня было то, что связка “человек залогинился -> CLI получил рабочий compute-доступ” в принципе есть.
Sber (физлица): контур для GIGACHAT_API_PERS другой
По публичной документации GigaChat API для персонального сценария:
-
используется
scope=GIGACHAT_API_PERS; -
access token выдаётся через
POST /api/v2/oauth; -
до этого нужен
Authorization key(изClient ID + Client Secret/ ключа проекта); -
токен короткоживущий (30 минут).
На практике для CLI это означает раздельные auth-контуры: пользовательский вход по SberID и доступ к compute API не образуют единую цепочку.
В публично описанном контуре отсутствует прямой механизм обмена пользовательского IDP-токена на compute-токен для GigaChat API.
Для персонального agentic UX уровня с непрерывным auth-контуром это и есть ключевое ограничение. Проще говоря: вошёл как пользователь, а дальше всё равно отдельная возня с app-ключами.
Почему в моей гипотезе сработал Yandex
Моя цель была узкой: сделать Codex CLI под российский инференс с login UX, максимально похожим на ChatGPT-поток.
Яндекс позволял проверить гипотезу сразу, потому что в документации есть явный и воспроизводимый мост между пользовательским OAuth и рабочим API-токеном.
Что пришлось менять в Codex CLI
Чтобы довести UX до рабочего состояния, пришлось пройти через несколько слоёв архитектуры Codex CLI.
1) TUI onboarding и конфиг провайдера
Что сделал:
-
добавил конфигурируемые OAuth-провайдеры;
-
вынес их в
config.toml(oauth_providers); -
собрал onboarding-экран в TUI для настройки и старта логина;
-
зафиксировал scope
cloud:auth, чтобы не ловить случайно кривые конфиги.
-
codex-rs/tui/src/onboarding/auth.rs -
codex-rs/tui/src/onboarding/onboarding_screen.rs -
codex-rs/core/src/config/mod.rs -
codex-rs/core/config.schema.json
2) OAuth runtime в отдельном login-слое
Что сделал:
-
универсальный старт OAuth-логина;
-
генерация authorize URL;
-
извлечение code;
-
обмен code на токены.
Отдельно поддержал два режима:
-
verification_code— нужен в текущем сценарии; -
loopback— оставил как универсальный путь для провайдеров, где возможен локальный callback.
3) Нормальный lifecycle токенов в AuthManager
Что сделал:
-
добавил
OauthAuthDataв auth storage; -
встроил OAuth в общий
AuthManager; -
добавил refresh-поток, чтобы сессия не рассыпалась после перезапуска/протухания access token.
-
codex-rs/core/src/auth/storage.rs -
codex-rs/core/src/auth.rs -
codex-rs/core/tests/suite/auth_refresh.rs
4) Провайдерный слой: довести логин до реального compute-доступа
Это самый важный кусок: логин сам по себе ничего не даёт, пока рантайм не получает корректный IAM + folder context.
Что сделал:
-
обмен OAuth token на IAM token;
-
резолв активной folder;
-
интеграция этого контекста в общий auth/runtime путь.
Ключевой момент: OAuth-логин не должен заканчиваться на уровне UI. Его нужно доводить до полноценного compute-контекста внутри рантайма Codex.
Здесь вынес всю специфическую логику в отдельный файл:
-
codex-rs/core/src/auth/yandex.rs
5) Нативная интеграция провайдера и моделей
Чтобы это было похоже на “родной” опыт, добавил провайдера как встроенный вариант и связал модельные пресеты с folder-aware slug.
-
codex-rs/core/src/model_provider_info.rs -
codex-rs/core/src/models_manager/manager.rs -
codex-rs/core/src/models_manager/model_presets.rs
Что получилось в итоге по UX
На стороне пользователя поток стал таким:
-
Запускаю
codex. -
Выбираю нужного провайдера.
-
Прохожу вход в браузере.
-
Возвращаюсь в CLI и ввожу
verification_code. -
CLI получает рабочий контекст и может идти в модель.
Субъективно это уже близко к нужному login first, work immediately, ради которого я и затевал первую часть эксперимента.
Инференсы Yandex моделей в рантайме Codex CLI
После логина всё выглядело красиво ровно до первого живого запроса. Дальше началась суровая реальность: вместо нормального агентного цикла я наблюдал сообщения от цензора Yandex с фразой CONTENT FILTER снова и снова. После этого разваливающийся tool-loop уже не удивлял.
6) Борьба с цензором и логирование запросов
На самом деле content_filter я увидел при первом же запросе: Codex отправляет отдельно developer сообщение со страшным словом shell да еще и в XML тегах. Не проблема, если склеить все developer сообщения в одно, то начинает проходить.
Но цензор только разминался. При втором запросе я снова словил content_filter. Почему? Зачем? Поэкспериментировав с историей, решил отдельно логировать сырые запросы в инференс на провайдере Yandex:
-
log_yandex_responses_request_payload(...)вcodex-rs/core/src/yandex_context_normalizer.rs; -
вызов этого логирования в
build_responses_request(...)(codex-rs/core/src/client.rs).
Что это дало: в логах сразу видно role, text_len, превью каждого ResponseItem. И там быстро всплыл корень боли.
7) Content filter бил не «случайно»: мы вторым запросом забивали контекст
По логам сессий в ~/.codex-yandex/log/codex-tui.log:
-
в проблемных запросах в
inputуходил огромный блок#AGENTS.mdinstructions ..., который честно собирал рантайм по всему проекту для контекста агента (text_lenпорядка18095); -
после этого шли ретраи и финал с
reason: content_filter.
То есть фильтр бил не по содержанию промпта, а по форме и объёму префикса контекста.
Чтобы это стабилизировать, появился normalize_responses_input_for_provider(...):
-
схлопывание стартовых служебных сообщений в компактный префикс;
-
вынос developer-контента в единый блок (склейка);
-
временный хак: вырезать AGENTS-полотно (то что вообще делает агента Codex агентом), но сохранить
<environment_context>.
Код: merge_initial_context_messages(...) и strip_agents_md_context_hack(...) в codex-rs/core/src/yandex_context_normalizer.rs.
8) Модель иногда отдавала tool calls в legacy-тексте, а не в структурных событиях
В ряде rollout-ов assistant присылал не нормальный function_call, а текст вида:
-
[TOOL_CALL_START]exec_command ... -
или fenced-блоки с
update_plan/exec_command.
Без нормализации это ломает агентный цикл, потому что рантайм ждёт поддержку tool API и структурный tool-call item.
Я не в первый раз встречаюсь с таким поведением (этим часто грешат китайские модели), поэтому не удивился и просто добавил преобразование в normalize_responses_output_items_for_provider(...):
-
парсинг
[TOOL_CALL_START]...; -
парсинг fenced tool-блоков;
-
ремонт кривого JSON (например
\'в аргументах shell); -
генерация валидных
ResponseItem::FunctionCallсcall_idформатаyandex-legacy-*.
9) Почему пришлось отключить параллельные tool calls
В Codex возможность параллельных вызовов инструментов определяется флагом parallel_tool_calls. Он подтягивается из model_info.supports_parallel_tool_calls (см. run_sampling_request(...) и сборку запроса в client.rs).
Проблема в том, что на старте Yandex-модель определялась как unknown slug (Unknown model gpt://.../yandexgpt/rc). А для неизвестных моделей Codex по умолчанию ставит supports_parallel_tool_calls = false.
В итоге цикл автоматически перешёл в последовательный режим. И, что интересно, это неожиданно стабилизировало поведение агента: пропали ситуации, когда модель в одном ответе генерировала несколько tool-команд и рантайм начинал спотыкаться о формат событий. Мы ведь помним, что Yandex нарушает tool API на второй-третий запрос, а поиск серии tool calls в ответе модели не выглядит стабильным решением.
По факту вывод простой: при интеграции стороннего провайдера лучше сначала добиться стабильного serial tool-loop. Параллелизм имеет смысл включать только тогда, когда формат ответов и событий гарантированно чистый и предсказуемый.
10) Отдельный кайф: Yandex уже на Responses API
Самый приятный бонус интеграции: не пришлось тащить костыли под legacy chat/completions, который команда Codex уже успела отключить в приложении. Откатывать логику ребят было бы странно.
В провайдере сразу зафиксировано:
-
wire_api = "responses"(create_yandex_providerвcodex-rs/core/src/model_provider_info.rs); -
supports_websockets = falseи обычный HTTP streaming путь.
Это сильно упростило архитектуру: я чинил только нормализацию и совместимость формата, а не весь транспортный слой Codex.
Что получилось после стабилизации
После всех правок пайплайн стал воспроизводимым:
-
OAuth/login и IAM/folder context собираются автоматически.
-
Запросы не валятся пачкой в
content_filterпо тем или иным причинам. -
Legacy tool-call текст приводится к структурным функциям, и агентный цикл живёт.
-
Inference работает на
Responses APIбез отката в старый режим.
Итого: самая сложная часть оказалась не в добавлении логина Yandex, а в адаптации Codex рантайма к реальным возможностям инференса.
Послевкусие
Интеграция заработала технически. Login собирается. IAM подтягивается. Responses API работает. Tool-loop после нормализации не разваливается на первом шаге.
Но ощущение “всё заработало” так и не появилось.
Формально интегрировать инференс — не значит получить полноценный агентный рантайм. Вопрос в агентных критериях. Если их нет, никакая обвязка не сделает систему агентной.
Я для себя сформулировал минимальный набор таких критериев.
1. Непрерывный auth-контур
Логин пользователя должен напрямую давать compute-доступ. Без разрыва между identity пользователя и инференсом.
Если после входа начинается отдельная возня с ключами — это не агентный UX.
2. Предсказуемость фильтрации
Инъекции — реальная угроза. Но когда инференс начинает реагировать на служебные теги или слова вроде shell как на атаку, это ломает инструментальный сценарий.
Защита не должна разрушать базовые инженерные use-case.
3. Контекстная устойчивость
Если несколько циклов reasoning приводят к деградации инструкций, потере политики или невозможности нормально использовать AGENTS.md / SKILL.md, это уже архитектурное ограничение.
Агентность невозможна без устойчивого управления контекстом.
4. Соблюдение tool API
Агентный рантайм строится на контракте. Если модель периодически “падает” в legacy-текст вместо структурных tool-calls, контракт нарушается. Тогда рантайм начинает чинить модель, вместо того чтобы выполнять задачу.
5. Следование инструкциям под нагрузкой (со звездочкой)
Самая тонкая тема — это не фильтр и не tool API, а устойчивость следования инструкциям в многошаговом цикле.
В моих экспериментах после 2–3 итераций reasoning модели начинали терять строгость: забывать ограничения, игнорировать формат, “переизобретать” правила, которые уже были заданы в начале сессии (установка python зависимостей через uv — это еще то приключение).
Я давно гоняю разные инференсы под агентной нагрузкой — от простых CLI-циклов до прогонов под агентные бенчмарки. И там всегда возникает ощущение: эта модель “держит форму” или нет. Появляется субъективное чувство — “твоя” она или нет.
Но когда модель обнуляется уже после пары циклов — это перестаёт быть субъективным ощущением. Это измеримый эффект деградации инструкционного слоя под нагрузкой. И речь не о качестве ответа и не о креативности.
Речь о способности сохранять контракт:
-
помнить ограничения,
-
соблюдать формат,
-
продолжать начатую стратегию,
-
не разрушать уже построенный контекст.
Если модель не удерживает инструкционную рамку в пределах одной агентной сессии, никакая обвязка её не спасёт. Агентность — это не “умный ответ”, это стабильное поведение в агентном цикле. И это, по сути, один из главных маркеров зрелости агентного слоя.
Соответствуют ли российские инференсы агентным требованиям?
YandexGPT Pro 5.1 (её я тестировал в Codex; Alice AI LLM позиционируется как чат-модель — tool-контракт и агентный reasoning она держит заметно хуже)
-
Непрерывный auth-контур собрать можно — плюс.
-
Responses API есть — плюс.
-
Tool API формально поддерживается — плюс (с поправкой на нормализацию и легаси-адаптацию).
-
Устойчивость контекста и поведение фильтра — спорный момент.
-
Долгоживущий reasoning без деградации — нестабильно.
Инфраструктурно Yandex сейчас ближе к агентному UX. Поведенчески ограничения начинают проявляться уже в многошаговом цикле.
GigaChat-2-Max (тестировался на стандартном Codex CLI через роутер)
-
Непрерывного auth-контрура нет — первый критерий не выполняется.
-
Контекст больше (заявленные 128k против ~32k у YandexGPT Pro 5.1) — плюс.
-
Tool API соблюдается чище, чем у Yandex — плюс.
-
Но деградация инструкционного слоя под нагрузкой ощущается сильнее.
То есть формально контракт может выглядеть аккуратнее, но после пары циклов reasoning модель начинает “расплываться” быстрее.
И это не раздача оценок, а попытка трезво посмотреть на зрелость агентного слоя через один и тот же рантайм.
Агентность — не про OAuth и не про наличие tool-calls. Это способность модели удерживать инструкционную рамку, не деградировать после нескольких итераций и соблюдать контракт с рантаймом. Если этого нет, мы получаем не агента, а чат с инструментами.
Codex CLI – это универсальный агентный рантайм. На его базе можно строить и бизнес-агентов, и исследовательские пайплайны, и автоматизацию. Но если модель не удерживает контракт в цикле, сфера применения не имеет значения — деградация всё равно проявится.
Что дальше?
С самого начала мне хотелось не оценки инференсам раздавать, а провести воспроизводимый эксперимент.
Интеграция показала: разные провайдеры живут в разных эпохах API.
-
У GigaChat — контракт раннего OpenAI, который уже даже не fully compatible.
-
У Yandex до Responses API был свой, мягко говоря, нестандартный completions.
-
У Codex — строгий рантайм, который ждёт нормальный Responses и корректный tool-контракт.
Чтобы не переписывать CLI под каждого вендора, я давно вынес адаптацию в отдельный слой — роутер. Сначала он жил в проде как утилитарная прокладка между разными контрактами. Потом я пересобрал его в чистую Rust-реализацию, чтобы можно было нормально экспериментировать с инференсами в Codex.
Роутер принимает Responses API и chat/completions, нормализует события, сглаживает различия провайдеров и позволяет подключать разные инференсы к Codex без переписывания CLI.
Проект роутера здесь:
https://github.com/olegische/xrouter
Fork Codex с Yandex здесь:
https://github.com/xrouter-ru/codex-yandex-provider
Если хотите проверить агентные критерии сами — подключайте через роутер Yandex, GigaChat или любой другой инференс и прогоняйте свои сценарии в реальном агентном цикле.
Такие эксперименты важны не только для пользователей. Они сталкивают провайдеров с реальными многошаговыми задачами, а не с синтетическими демо-кейсами. Под такой нагрузкой быстрее всего проявляются ограничения и растёт зрелость экосистемы.
Автор: olegische


