Codex, Claude Code и Gemini CLI через сервер Chat Completions
Эта инструкция подключает один неизменяемый OpenAI-совместимый сервер
/v1/chat/completions сразу к трём клиентским протоколам:
Codex (Responses) ───────────┐
Claude Code (Messages) ──────┼─> gpt2giga ─> /v1/chat/completions
Gemini CLI (GenerateContent) ┘
Маршрут имеет статус technical preview. gpt2giga переводит только проверенное чат-подобное подмножество, а запрос с непереносимым смыслом отклоняет до обращения к вышестоящему серверу. Шлюз не выдаёт Chat Completions за полную реализацию Responses, Messages или GenerateContent.
Требования к вышестоящему серверу
Понадобятся:
- точный Chat Completions endpoint либо базовый URL API;
- точный идентификатор модели на сервере;
- реальные лимиты контекста, входных и выходных токенов;
- при необходимости bearer-токен;
- обычный JSON-ответ Chat Completions;
- либо Chat Completions SSE с завершающим кадром
data: [DONE], либо явно включённый буферизованный режим из раздела ниже; - function tools и tool calls, если кодовые агенты должны вызывать локальные инструменты.
reasoning_effortи текстовыйreasoning_content, если рассуждение Codex должно переживать перевод Responses и следующий ход с результатом tool call.
В стандартном SSE-режиме шлюз передаёт
stream_options: {"include_usage": true}. В буферизованном режиме он вместо
этого отправляет stream: false и использует usage из полного JSON-ответа.
Если сервер не вернул usage, gpt2giga не придумывает значения токенов.
1. Опишите upstream
Создайте providers.yaml вне репозитория:
schema_version: gpt2giga.provider-profiles.v3
profiles:
- profile_id: chat-only-upstream
provider_kind: openai_compatible
base_url: https://inference.example/v1/chat/completions
credential_env: CHAT_UPSTREAM_API_KEY
network_policy_ref: public-openai
tls_policy_ref: system-default
models:
- public_alias: bridge/chat-only
upstream_model: exact-upstream-model-id
capability_profile: chat-only-coding-v1
capabilities:
features:
- roles
- ordered_content_parts
- text
- generation_controls
- reasoning_controls_and_summaries
- function_tools
- tool_choice
- parallel_tool_calls
- tool_results
- stream_deltas
- stream_terminal_events
- stop_reason
- usage
- model_identity
- request_error_classes
- cancellation
- context_token_limits
limits:
context_window: 32768
max_input_tokens: 28672
max_output_tokens: 4096
support_status: technical_preview
Замените endpoint, модель и лимиты. public_alias — имя модели, которое будут
использовать все три клиента. В upstream_model оно преобразуется только
внутри шлюза.
Если сервер намеренно работает без ключа, удалите credential_env. Когда поле
задано, gpt2giga читает указанную переменную при запуске и отправляет её
значение как bearer-токен. Клиент не может подменить адрес, внутреннюю модель
или учётные данные upstream.
Список features — это список реально проверенных возможностей, а не
пожеланий. Добавляйте json_schema_output или image_references только после
проверки конкретной модели. Не добавляйте count_tokens: у одного Chat
Completions endpoint нет точной операции подсчёта токенов.
Указывайте reasoning_controls_and_summaries, только если модель принимает
стандартный верхнеуровневый reasoning_effort и возвращает текстовый
reasoning_content. Предпочтителен нативный повтор этого поля в assistant-
сообщении с tool call. Если chat template upstream отклоняет такую историю с
подтверждённой 5xx-ошибкой, описанной ниже, gpt2giga может продолжить через один
встроенный текстовый повтор. Допустимые effort зависят от модели. Например,
модель может принимать xhigh, но отклонять max; используйте максимальное
значение, проверенное на этом конкретном endpoint.
Можно указать и базу https://inference.example/v1: тогда gpt2giga добавит
chat/completions. Полный адрес, заканчивающийся на /chat/completions,
используется без изменения.
Буферизованные ответы upstream
Некоторые OpenAI-совместимые серверы принимают stream: true, но всё равно
возвращают один полный Chat Completion с типом application/json. Для такого
сервера явно включите отложенный синтез потока в профиле v3:
upstream_stream_mode: buffered
Шлюз отправит upstream-параметр stream: false, проверит полный ответ и выдаст
клиенту одну текстовую дельту или по одной полной дельте на function call,
после чего передаст завершающее событие и usage. Этого достаточно для обычных
циклов с инструментами в Codex, Claude Code и Gemini CLI.
Буферизованный режим не даёт поток токенов от модели. Первое событие с текстом
или function call приходит только после завершения полного ответа, поэтому в
этом режиме нельзя измерять TTFT модели и инкрементальную отмену. По умолчанию
используется строгий sse: gpt2giga не включает буферизацию автоматически,
если upstream нарушил заявленный streaming-контракт. Специфичные для провайдера
поля не входят в мост без явно заявленной capability. При
reasoning_controls_and_summaries поле reasoning_content выдаётся перед
ответом или function call как Responses reasoning summary и в настоящем SSE,
и в буферизованном режиме.
Локальный upstream
Для сервера на той же машине используйте явное исключение для loopback:
base_url: http://127.0.0.1:8001/v1/chat/completions
allow_loopback: true
network_policy_ref: loopback-development
upstream_stream_mode: buffered # только если у сервера нет настоящего SSE
Прямые адреса из частных, link-local и metadata-сетей отклоняются. Если сервер доступен только через VPN или частную подсеть, поднимите SSH-туннель или его аналог на loopback и укажите локальный адрес в профиле.
Docker с mitmproxy в reverse mode
Обычный стек deploy/observability.yaml настраивает forward proxy через
HTTP_PROXY и HTTPS_PROXY. OpenAI-compatible adapter намеренно не читает эти
переменные окружения. Чтобы запустить мост в Docker и видеть каждый upstream-
запрос, используйте отдельный reverse-mode стек:
cp .env.example .env
# Перед запуском задайте GPT2GIGA_API_KEY в .env.
docker compose --env-file .env \
-f deploy/chat-completions-observability.yaml up -d --build
Стек монтирует deploy/providers.chat-completions.example.yaml, где gateway
отправляет запросы на http://127.0.0.1:8080/v1/chat/completions. gpt2giga и
mitmproxy делят один network namespace, поэтому этот loopback ведёт в
mitmproxy. По умолчанию mitmproxy пересылает запрос на Docker host по адресу
http://host.docker.internal:29999. При необходимости переопределите оба
значения в .env:
MITMPROXY_UPSTREAM_URL=http://host.docker.internal:29999
GPT2GIGA_PROVIDER_CONFIG_FILE=/absolute/path/to/providers.yaml
Пример не заявляет parallel_tool_calls. Добавляйте эту capability только
после того, как upstream вернёт два независимых function call в одном ходе
assistant и примет результаты обоих инструментов на следующем ходе.
Туннель на host-машине должен уже принимать запросы на порту 29999. Docker не может починить или заменить этот туннель. После запуска:
curl -fsS http://127.0.0.1:8090/ready
curl -fsS \
-H "Authorization: Bearer $GPT2GIGA_API_KEY" \
http://127.0.0.1:8090/v1/models
Интерфейс mitmproxy доступен на http://127.0.0.1:8081. Для остановки
используйте down с тем же Compose-файлом.
2. Запустите gpt2giga
Из рабочей копии репозитория:
export CHAT_UPSTREAM_API_KEY='<upstream-bearer-token>'
export GPT2GIGA_CONFIG=/absolute/path/to/providers.yaml
export GPT2GIGA_ENABLE_API_KEY_AUTH=True
export GPT2GIGA_API_KEY='<local-gateway-key>'
uv run gpt2giga
Для upstream без ключа не задавайте CHAT_UPSTREAM_API_KEY. Если пакет уже
установлен, запускайте gpt2giga вместо uv run gpt2giga.
Проверьте готовность и статический алиас модели — эти два запроса не обращаются к inference-серверу:
curl -fsS http://127.0.0.1:8090/ready
curl -fsS \
-H "Authorization: Bearer $GPT2GIGA_API_KEY" \
http://127.0.0.1:8090/v1/models
Затем проверьте реальный перевод:
curl -fsS http://127.0.0.1:8090/v1/responses \
-H "Authorization: Bearer $GPT2GIGA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"bridge/chat-only","input":"Reply only OK"}'
3. Подключите Codex
Добавьте провайдера в ~/.codex/config.toml:
[model_providers.gpt2giga_chat]
name = "gpt2giga Chat bridge"
base_url = "http://127.0.0.1:8090/v1"
env_key = "GPT2GIGA_API_KEY"
wire_api = "responses"
requires_openai_auth = false
supports_websockets = false
Создайте отдельный файл профиля ~/.codex/chat-bridge.config.toml:
model = "bridge/chat-only"
model_provider = "gpt2giga_chat"
model_context_window = 32768
model_auto_compact_token_limit = 24576
model_reasoning_summary = "auto"
model_reasoning_effort = "xhigh"
web_search = "disabled"
Экспортируйте локальный ключ и выберите профиль:
export GPT2GIGA_API_KEY='<local-gateway-key>'
codex --profile chat-bridge
Минимальная команда Codex для бенчмарка
Для герметичного CLI-бенчмарка отключите пользовательские MCP, приложения, плагины и multi-agent, а настройки моста передайте явно:
codex -a never exec --json \
--ignore-user-config \
--strict-config \
--skip-git-repo-check \
--ephemeral \
--sandbox workspace-write \
--disable apps \
--disable plugins \
--disable multi_agent \
--disable apply_patch_freeform \
-m bridge/chat-only \
-c model_provider=gpt2giga_chat \
-c model_providers.gpt2giga_chat.name=gpt2giga_chat \
-c model_providers.gpt2giga_chat.base_url=http://127.0.0.1:8090/v1 \
-c model_providers.gpt2giga_chat.env_key=GPT2GIGA_API_KEY \
-c model_providers.gpt2giga_chat.wire_api=responses \
-c model_providers.gpt2giga_chat.supports_websockets=false \
-c model_context_window=32768 \
-c model_auto_compact_token_limit=24576 \
-c model_reasoning_summary=auto \
-c model_reasoning_effort=xhigh \
-c web_search=disabled \
'Выполни задачу бенчмарка.'
Эту же строку можно передать в runner, например в harness_bench run-cli.
Codex CLI 0.146.0 проверен с таким минимальным набором:
exec_command, write_stdin, update_plan, request_user_input и
view_image. Все они передаются как обычные function tools. В живой
двухходовой проверке Codex получил потоковый exec_command, выполнил его,
вернул результат и получил финальный ответ модели через upstream Chat
Completions.
Codex CLI 0.146.0 отклоняет model_supports_reasoning_summaries при включённом
--strict-config, поэтому в закреплённой команде выше этого ключа нет. Эта
версия принимает model_reasoning_summary и model_reasoning_effort; для
проверенной модели effort достаточно, чтобы получить reasoning_content.
После обновления Codex перепроверяйте strict-config ключи: опубликованная
справка может описывать более новую версию CLI.
Responses custom/freeform tools намеренно не входят в поддержанное подмножество.
В частности, не включайте apply_patch_freeform. Для неизвестного публичного
алиаса из примера текущий Codex редактирует файлы через exec_command и не
выставляет apply_patch. Если бенчмарк проверяет именно wire-контракт custom
apply_patch, нужен нативный Responses upstream или отдельно рассмотренное
расширение для custom tools.
Codex работает по Responses API. Шлюз принимает его stateless-envelope,
переводит сообщения developer в system для Chat Completions, разворачивает
namespace tools перед вызовом upstream и восстанавливает namespace в
вернувшихся function calls. Он принимает служебные item id, которые Codex
повторяет в истории, и сохраняет идентификатор вызова до следующего
function_call_output.
Когда reasoning включён, шлюз передаёт effort из Responses как Chat
Completions reasoning_effort. Вернувшийся reasoning_content становится
элементом reasoning с одной частью summary_text. Если Codex повторит этот
элемент перед function call, gpt2giga восстановит текст как assistant
reasoning_content в следующем запросе Chat Completions. Обычная stateless-
подсказка include: ["reasoning.encrypted_content"] принимается, но шлюз не
утверждает, что создаёт зашифрованное reasoning-состояние OpenAI: для повторной
передачи используется текстовый summary.
Это явно обозначенная совместимая проекция текста рассуждения провайдера, а не summary, созданный OpenAI. Upstream из проверенного примера не возвращает пригодное число reasoning-токенов, поэтому gpt2giga оставляет эту детализацию недоступной и не пытается вычислить её из текста.
Сломанный chat template для истории инструментов
Некоторые inference-серверы принимают первый function call, но падают при
отрисовке assistant tool calls и tool results в следующем запросе, например с
Failed to apply chat template: Object of type Undefined is not JSON serializable. gpt2giga обрабатывает это внутри OpenAI-compatible адаптера;
скрипт mitmproxy и отдельная compatibility-переменная окружения не нужны.
Сначала всегда отправляется исходный запрос в стандартной форме. Только если
тот же профиль и модель возвращают 5xx с Failed to apply chat template в
detail, а в запросе есть история инструментов, gpt2giga делает один повтор.
В нём сохраняются текущие определения инструментов, tool choice, reasoning
effort и параметры генерации, а предыдущие assistant reasoning, function calls
и tool results переводятся в явный текст. Результаты инструментов помечаются
как недоверенные данные. После этого модель может вызвать следующий обычный
инструмент или завершить задачу.
Остальные ошибки не повторяются. Не повторяется и SSE-ошибка после первого
content chunk от upstream. gpt2giga не выполняет, не переименовывает и не
придумывает инструменты, а вторую ошибку возвращает клиенту. Успешное
восстановление отмечается как
metadata.gpt2giga_chat_template_fallback="tool_history_text_replay"; маркер
также есть в начальном и завершённом объектах потока Responses.
Codex запрашивает /v1/models?client_version=... в собственном формате
каталога, который включает принадлежащие Codex базовые инструкции и метаданные
инструментов. На такой запрос gpt2giga возвращает корректный пустой каталог
Codex, не обращаясь к серверу инференса. Для явно выбранной пользовательской
модели Codex сохраняет собственные fallback-инструкции и метаданные
инструментов, а явные значения context и compaction выше заменяют fallback-
лимиты токенов. Обычные OpenAI-клиенты по-прежнему получают статические алиасы
в стандартном списке data. При работе через этот мост всегда передавайте
-m или задавайте model: пустой каталог Codex не может выбрать клиенту
модель по умолчанию.
Поля конфигурации и приоритет файлов профилей описаны в официальной справке Codex.
4. Подключите Claude Code
Оставьте Claude Code в stateless-подмножестве Messages и синхронизируйте лимиты с профилем провайдера:
export ANTHROPIC_BASE_URL=http://127.0.0.1:8090
export ANTHROPIC_API_KEY="$GPT2GIGA_API_KEY"
export ANTHROPIC_MODEL=bridge/chat-only
export ANTHROPIC_SMALL_FAST_MODEL=bridge/chat-only
export CLAUDE_CODE_MAX_CONTEXT_TOKENS=32768
export CLAUDE_CODE_MAX_OUTPUT_TOKENS=4096
export MAX_THINKING_TOKENS=0
export CLAUDE_CODE_DISABLE_THINKING=1
export DISABLE_PROMPT_CACHING=1
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
claude --model bridge/chat-only
Через ANTHROPIC_API_KEY Claude Code отправляет локальный ключ шлюза в
x-api-key. gpt2giga также принимает bearer-аутентификацию.
Thinking и prompt caching отключены, потому что Chat Completions не может
сохранить эту семантику Anthropic. Значение
CLAUDE_CODE_MAX_OUTPUT_TOKENS не должно превышать
capabilities.limits.max_output_tokens: для неизвестной Claude-модели клиент
по умолчанию запрашивает существенно больший ответ. Обычные function tools и
потоковые Messages events через этот маршрут работают.
Некоторые сценарии Claude Code вызывают /v1/messages/count_tokens. Мост
отклоняет такую операцию вместо неточной оценки. Основной чат и вызовы tools не
требуют отдельного count-token endpoint, но зависимая от него функция останется
недоступна.
Актуальный список клиентских переменных находится в официальной справке Claude Code.
5. Подключите Gemini CLI
Для неизвестной модели Gemini CLI применяет встроенную конфигурацию
chat-base, которая сейчас добавляет topK и thinkingConfig. У этих
параметров нет точного представления в Chat Completions, поэтому мост их
отклоняет. Нужен минимальный пользовательский алиас с ключом, точно совпадающим
с публичным id модели.
Добавьте этот блок в ~/.gemini/settings.json, не удаляя остальные настройки:
{
"security": {
"auth": {
"selectedType": "gemini-api-key"
}
},
"modelConfigs": {
"customAliases": {
"bridge/chat-only": {
"modelConfig": {
"model": "bridge/chat-only",
"generateContentConfig": {
"temperature": 1,
"topP": 0.95,
"maxOutputTokens": 4096
}
}
}
}
}
}
Не наследуйтесь от chat-base и не добавляйте topK либо thinkingConfig для
этого маршрута. Ключ алиаса должен оставаться bridge/chat-only: после вызова
инструмента Gemini CLI продолжает работу с разрешённым id модели, поэтому алиас
с другим именем повлияет только на первый запрос.
Запускайте Gemini CLI с публичным id модели:
export GOOGLE_GEMINI_BASE_URL=http://127.0.0.1:8090
export GEMINI_API_KEY="$GPT2GIGA_API_KEY"
export GEMINI_MODEL=bridge/chat-only
export OTEL_SDK_DISABLED=true
gemini --model bridge/chat-only
Gemini передаёт GEMINI_API_KEY в Google-совместимой форме, которую шлюз
принимает как свой локальный API key. Профиль провайдера затем связывает
публичный id с точной моделью upstream. Синтетический маркер Gemini CLI
skip_thought_signature_validator принимается только как клиентский no-op;
настоящая thought signature остаётся неподдержанной.
Если задан GEMINI_CLI_HOME, Gemini CLI считает его корнем домашней папки.
Глобальные настройки должны находиться в
$GEMINI_CLI_HOME/.gemini/settings.json, а не прямо в $GEMINI_CLI_HOME.
Если алиас хранится в проектном .gemini/settings.json, сначала доверьте
рабочую папку клиенту. Для headless-запуска задайте
GEMINI_CLI_TRUST_WORKSPACE=true до старта Gemini CLI, чтобы проектные настройки
были загружены.
Формат описан в официальной справке Gemini CLI по model configuration.
Поддержанное подмножество и ограничения
Мост поддерживает текстовые роли, общие параметры генерации, function tools и их результаты, tool choice там, где целевой протокол умеет его выразить, потоковые ответы, terminal stop reasons, usage при наличии в upstream, отмену, заявленные лимиты контекста и включаемые через профиль Responses reasoning controls с повторной передачей summary.
Поддержан полный stateless-цикл инструмента: клиент передаёт определения функций, получает потоковый или обычный вызов, выполняет его локально и в следующем запросе повторяет вызов вместе с результатом. Мост сохраняет call id и переводит следующий ход обратно в Chat Completions. Сценарий проверен для Responses, Anthropic Messages и Gemini GenerateContent.
Если OpenAI-compatible upstream возвращает для этой истории подтверждённую 5xx-ошибку chat template, адаптер выполняет описанный выше однократный текстовый повтор. Восстановление общее для фасадов Responses, Messages и GenerateContent, потому что выполняется после их перевода в один нормализованный путь Chat Completions.
Мост не эмулирует:
- состояние Responses:
previous_response_id, conversations, background jobs и долговременныйstore; - нативное зашифрованное reasoning-состояние, независимые от провайдера thinking controls и точный учёт reasoning-токенов;
- семантику prompt cache и точный cached-token accounting;
- hosted web search, computer use, code execution и provider-native tools;
- Responses custom/freeform tools, например
apply_patch; - files, audio и неподдерживаемые мультимодальные данные;
- точный подсчёт токенов;
- Gemini safety settings, cached content,
topKи thinking configuration.
Проверенные служебные no-op поля клиентов принимаются, если они не меняют
переведённый запрос. Семантическое поле вне заявленного подмножества возвращает
unsupported_semantic до обращения к upstream.
Диагностика
| Симптом | Что делать |
|---|---|
unknown_model_alias | Используйте точный public_alias с тем же регистром и знаками. |
credential_unavailable при запуске | Задайте переменную из credential_env или удалите поле для намеренно открытого upstream. |
unsupported_semantic | Клиент запросил смысл, который нельзя сохранить в Chat Completions. Отключите эту функцию; не добавляйте capability без реальной проверки upstream. |
| Отказ по токенным лимитам | Синхронизируйте context/output клиента с точными значениями профиля. |
В запросе Gemini есть topK или thinkingConfig | Задайте минимальный пользовательский алиас под точным публичным id bridge/chat-only: алиас с другим именем действует только на первый запрос tool-loop. |
Claude вызывает count_tokens | Для upstream только с Chat Completions эта операция намеренно не поддерживается. |
| Codex предупреждает о метаданных модели | Оставьте явные context/compaction значения в профиле Codex; обычные запросы продолжат работу. |
Upstream отклоняет reasoning_effort=max | Набор effort зависит от модели. Используйте максимальное проверенное значение, например xhigh, и привязывайте capability profile к этой модели. |
В metadata ответа есть gpt2giga_chat_template_fallback=tool_history_text_replay | Upstream отклонил нативную историю инструментов, а однократный встроенный текстовый повтор сработал. Слой совместимости mitmproxy не участвовал. |
Клиент получил chat_template_application_failed | В запросе не было истории для повтора или единственная fallback-попытка тоже завершилась ошибкой. Исправьте или замените chat template upstream; повторной попытки не будет. |
provider_protocol_error в потоке | Проверьте корректный Chat Completions SSE, terminal choice, порядок optional usage и завершающий [DONE]. |
Полная схема находится в разделе «Профили провайдеров и алиасы моделей», а правила семантического допуска — в разделе «Совместимость провайдеров».