opencode-jev-router — opencode сам выбирает глубину размышлений
📂 Исходный код на GitHubПлагин для opencode и отдельный HTTP-прокси (MIT), которые спрашивают у модели Jev, какой уровень размышлений нужен на каждый запрос, и подставляют его в запрос к GPT-6 или Claude. Простые правки уходят модели с низким уровнем, сложные — с высоким, и качество ответов не падает
Плагин для opencode, который снимает с вас ручной выбор глубины размышлений. Перед каждым запросом он спрашивает у модели Jev, сколько размышлений нужно именно на эту задачу, и подставляет выбранный уровень в запрос к основной модели. Переименование переменной получит low, разбор падающего теста — high.
Проект написан на TypeScript, распространяется под лицензией MIT и опубликован в npm как @robertn702/opencode-jev-router. Открытый код: репозиторий — robertn702/opencode-jev-router.
Зачем это нужно
У reasoning-моделей есть уровень размышлений. Он напрямую влияет на скорость ответа и на расход токенов. Обычно его задают один раз в настройках модели — и живут с этим.
Но задачи в opencode очень разные по сложности. Переименовать переменную и разобраться, почему падает тест, — это разный объём работы. На простой задаче высокий уровень сжигает токены впустую. На сложной низкий уровень не даёт добраться до причины. Авторы проекта предлагают решать это не вручную, а отдельной дешёвой моделью.
Как это работает
Jev — не обычная LLM. Это специализированная модель класса System One от TypeSafe. Вместо развёрнутого текста она возвращает типизированный выбор с откалиброванными вероятностями за доли секунды. Здесь её используют ровно для одной задачи: выбрать уровень размышлений.
Дальше по шагам:
- Плагин отправляет Jev ограниченную сводку последних сообщений диалога. Jev возвращает уровень: например
lowдля переименования иhighдля падающего теста. - Выбор добавляется в запрос отдельным элементом
configuration_updateот OpenAI. Для Claude используется системное сообщение, которое меняет только уровень. Старые обновления остаются на месте, чтобы начало промпта не ломалось о кеш. - Запрос уходит на ваш endpoint, ответ возвращается без изменений.
Если Jev не ответил или ответил медленно, запрос уходит с резервным уровнем. По умолчанию это high.
Что нужно для запуска
- opencode V2 версии 2.0.4 или новее. Проверено на 2.0.4 и 2.0.18. Для opencode V1 есть отдельная версия
@robertn702/opencode-jev-router@0.5, либо можно использовать прокси (см. ниже). - Ключ Jev. Либо напрямую от TypeSafe, либо через Vercel AI Gateway. Каждый запрос к модели даёт один вызов классификатора, и он тарифицируется отдельно.
- Endpoint, который говорит на Responses API. Он должен отдавать GPT-6 Astra, Luna или Sol и принимать элементы
configuration_update, плюс нужен ключ к нему. Официальный API OpenAI подходит. Подойдёт и любой шлюз с тем же интерфейсомPOST /v1/responses, например CLIProxyAPI, который работает по подписке ChatGPT. - Для Claude вместо GPT-6: endpoint Anthropic Messages API с одной из пяти перечисленных ниже моделей и доступом к бета-возможности смены настроек в середине диалога.
Node.js версии 24.x нужен только для отдельного прокси и для разработки. Сам плагин запускается вместе с opencode.
Быстрый старт
1. Экспортируйте ключи в том терминале, где запускается opencode:
export JEV_API_KEY=... # TypeSafe key or Vercel AI Gateway key
export OPENAI_API_KEY=... # key for your Responses API endpoint
2. Добавьте плагин в конфиг opencode. Это либо ~/.config/opencode/opencode.json для всех проектов, либо opencode.json в корне проекта:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [{ "package": "@robertn702/opencode-jev-router", "options": {
"jevApiKey": "{env:JEV_API_KEY}",
"jevBaseUrl": "https://ai-gateway.vercel.sh/typesafe",
"wrap": { "openai": ["openai/gpt-6-astra"] },
"decisionsLogPath": "/tmp/jev-decisions.jsonl"
}}],
"model": "jev-router/gpt-6-astra"
}
Если у вас ключ от TypeSafe напрямую, удалите строку jevBaseUrl. Ключ работает только со своим endpoint.
{env:NAME} читает переменную окружения, а {file:~/path} читает файл — если удобнее держать ключи на диске.
Готовый пример конфига лежит в репозитории: examples/opencode.jsonc.
3. Перезапустите opencode и убедитесь, что модель появилась как jev-router/gpt-6-astra. Список моделей можно открыть командой /models.
4. Проверьте, что маршрутизация работает. Отправьте любой запрос, затем выполните:
tail -n 1 /tmp/jev-decisions.jsonl
В выводе должно быть событие JevDecision с заполненным полем "effort" и значением "fallback": null. Непустое fallback означает, что до Jev не дошли. Строку decisionsLogPath можно убрать, когда всё заработало, либо оставить и собирать метрики.
Свой шлюз подключается так же: описываете его как провайдер в opencode и оборачиваете его модель. Обычный http:// допустим только для адресов на локальной машине.
Поддерживаемые модели
Плагин регистрирует псевдонимы вида jev-router/<профиль> только для тех моделей, которые перечислены в wrap. Исходные модели при этом не меняются. Псевдонимы наследуют маршрут, настройки, заголовки, лимиты и стоимость исходной модели, но ручных вариантов уровня у них нет.
| Модель в opencode | Уровни, которые может выбрать Jev |
|---|---|
jev-router/gpt-6-astra |
low, medium, high, xhigh, max |
jev-router/gpt-6-luna |
none, low, medium, high, xhigh, max |
jev-router/gpt-6-sol |
none, low, medium, high, xhigh, max |
jev-router/gpt-6.1-sol |
low, medium, high, xhigh, max |
jev-router/claude-fable-5-1 |
low, medium, high, xhigh, max |
jev-router/claude-mythos-5-1 |
low, medium, high, xhigh, max |
jev-router/claude-opus-5-5 |
low, medium, high, xhigh, max |
jev-router/claude-opus-5 |
low, medium, high, xhigh, max |
jev-router/claude-sonnet-5-5 |
low, medium, high, xhigh, max |
Что уходит в Jev, а что остаётся у вас
Это главный вопрос при подключении любого внешнего классификатора, и авторы проекта отвечают на него прямо.
- В Jev уходят ограниченные фрагменты последних сообщений пользователя и ассистента, до восьми последних результатов инструментов с именами и признаком ошибки, короткая сводка о сбое и идентификатор модели. Данные от размещённых на стороне инструментов и от computer use не отправляются.
- На ваш endpoint уходит полный запрос opencode с добавленным обновлением уровня.
- Локально по умолчанию ничего не пишется. Если задан
decisionsLogPath, туда попадает только метаинформация: идентификаторы, модель, уровень, задержка и счётчики токенов. Промпты, вывод инструментов, ключи и сырые ошибки не логируются.
Настройки
Параметры плагина живут в поле options у записи plugins.
| Параметр | По умолчанию | Зачем |
|---|---|---|
jevApiKey |
переменная JEV_API_KEY |
Ключ классификатора Jev. Обязателен |
jevBaseUrl |
https://api.typesafe.ai |
Для ключа Vercel поменять на https://ai-gateway.vercel.sh/typesafe. Другие значения не принимаются |
wrap |
нет | Обязательный непустой объект с массивами openai и/или anthropic |
decisionsLogPath |
выключено | Абсолютный путь для файла JevDecision в формате JSONL |
baseEffort |
medium |
Уровень, который отдаётся в ответе на уровне всего запроса |
jevTimeoutMs |
4000 |
Общий бюджет времени на классификацию вместе с повторами |
maxRetries |
1 |
Сколько дополнительных попыток при временных ошибках Jev |
fallbackMode |
fixed |
fixed, previous или error — последнее означает отказ запроса |
fallbackEffort |
high |
Уровень, который используется, если классификация не удалась |
maxRequestBytes |
1048576 |
Максимальный размер тела запроса |
maxInFlight |
32 |
Сколько запросов может идти одновременно |
upstreamHeaderTimeoutMs |
10000 |
Сколько ждать заголовки ответа от основной модели |
upstreamIdleTimeoutMs |
60000 |
Максимальная пауза между кусками потока |
Опции maxRetries, fallbackMode и fallbackEffort появились в версии 0.3.0. Подробности про повторы и резервное поведение описаны в документе о политике классификации.
Если Jev недоступен
Резервный путь срабатывает автоматически, и это самое частое, что стоит проверить в первую очередь.
| Что видите | Что делать |
|---|---|
В логе fallback равно jev_error с jev_error_category: "http_auth" |
Ключ Jev не подходит к endpoint. Для ключа Vercel нужен jevBaseUrl, для ключа TypeSafe его надо убрать |
В логе fallback равно jev_timeout |
Jev отвечал медленно или был недоступен. Запросы всё равно ушли с резервным уровнем. Если это происходит часто, поднимите jevTimeoutMs |
В логе opencode failed to load plugin |
Проверьте синтаксис wrap и уберите опции, удалённые в новых версиях |
Model unavailable: jev-router/... |
Плагин не загрузился. Смотрите запись plugins и лог opencode |
| Ответ модели 401, 403 или 404 | Ответ endpoint передаётся без изменений. Проверяйте ключ провайдера и имя модели |
Локальная ошибка 400 про reasoning.mode или truncation |
Запрос использует неподдерживаемый режим или модель |
uses OAuth или has no API key |
Встроенный OAuth по подписке не поддерживается. Нужен ключ API либо шлюз с ключевой авторизацией, например CLIProxyAPI |
| Изменения конфига ничего не дали | Перезапустите opencode. Плагины загружаются только при старте |
Прокси вместо плагина
Тот же роутер можно запустить как локальный HTTP-прокси для любого клиента Responses API. Это нужно Node.js 24.x.
Создайте файл .env в папке, из которой будете запускать, или экспортируйте переменные:
JEV_API_KEY=your-jev-key
# JEV_BASE_URL=https://ai-gateway.vercel.sh/typesafe # Vercel keys only
JEV_ROUTER_UPSTREAM_BASE_URL=https://api.openai.com/v1
JEV_ROUTER_UPSTREAM_AUTH=bearer
JEV_ROUTER_UPSTREAM_API_KEY=your-endpoint-key
Запуск и проверка:
npx --yes @robertn702/opencode-jev-router
curl --fail http://127.0.0.1:4320/ready
curl http://127.0.0.1:4320/v1/responses \
-H 'content-type: application/json' -H 'authorization: Bearer unused' \
-d '{"model":"gpt-6-astra","input":[{"role":"user","content":"Say hi"}]}'
Прокси слушает http://127.0.0.1:4320. Для Anthropic задайте JEV_ROUTER_ANTHROPIC_UPSTREAM_BASE_URL и JEV_ROUTER_UPSTREAM_API_KEY для режима bearer, а запросы отправляйте на POST /v1/messages. Каждый запрос печатает в stdout запись с метаинформацией. Полный список переменных — по npx @robertn702/opencode-jev-router --help, значения по умолчанию — в .env.example.
До версии 0.3.0 переменные назывались без префикса JEV_ROUTER_. Разница описана в CHANGELOG.
Что показали замеры
Замеры авторов приведены вместе с их оговорками — приводим их без изменений.
- На задаче pytest #5262 Jev потратил на 42% меньше выходных токенов и на 40% меньше времени, чем фиксированный
high. Обе настройки решили задачу в 6 случаях из 6. - На отдельно выбранной сложной задаче Jev решил 5 случаев из 5, а фиксированный
medium— только 1 из 5. Авторы прямо пишут, что такой разовый результат не доказывает общую пользу. - На наборе задач GPT-6 Astra Jev и фиксированный
highрешили по 44 попытки из 44. Jev при этом использовал на 14% меньше выходных токенов вместе с размышлениями и в среднем заканчивал на 9% быстрее.
На других задачах результат может отличаться — это написано в README проекта. Подробные цифры и методика лежат в описании замеров.
Ограничения
Для GPT-6 поддерживается только обычный режим с одним агентом. Pro-модели, другие значения reasoning.mode и режим truncation: "auto" отклоняются на месте. Собственные варианты уровня размышлений в opencode для этого провайдера игнорируются, потому что уровень выбирает Jev. В ответе при этом указывается базовый уровень medium, а не выбранный — смотреть нужно в журнал решений.
Встроенный OAuth по подписке ChatGPT и Claude не поддерживается. Нужен ключ API либо шлюз с ключевой авторизацией.
Запросы к Anthropic не следуют редиректам: ответ с кодом 3xx возвращается вызывающему, чтобы ключи не ушли на другой домен. Данные о том, как меняется уровень и что происходит с кешем промпта, собраны в документе о поведении.
Плагину нужен транспорт HTTP. Если вручную переопределить его на websocket, плагин не станет работать молча — он честно откажется обрабатывать запрос.
Лицензия и предшественники
Код распространяется под лицензией MIT. Идея подсмотрена в jev-codex-router и pi-jev-router — авторы указывают, что код оттуда не копировали. Пакет лежит в npm: @robertn702/opencode-jev-router.