0.167.25
Fixed
- max_completion_tokens отправлялся всем бэкендам безусловно, а extra_body молча выбрасывался (#299 — P3). (1)
buildParamsвсегда ставилparams.MaxCompletionTokens— бэкенды, принимающие только классическийmax_tokens(часть сборок vLLM, LM Studio, отдельные шлюзы), отвергали запрос как unknown parameter либо молча игнорировали лимит, оставляя генерацию неограниченной. Теперь wire-поле выбирается поProviderSpec.SupportsMaxCompletionTokens(реестр:trueтолько у OpenAI; остальные получаютmax_tokens, который принимают все OpenAI-совместимые), а неположительный лимит (maxTokens <= 0) не отправляется вовсе. (2)extraBodyиз конфигурации (models.extra_body/llm_providers.extra_body) парсился и отбрасывался параметром-заглушкой_конструктора — настройка выглядела работающей. Теперь карта сохраняется в провайдере (с defensive-копией: buildParams читает её без локов) и мерджится в тело запроса черезSetExtraFields. Правило приоритета: типизированные поля запроса выигрывают — конфликтующие ключи extra_body отбрасываются с WARN (SetExtraFields иначе молча переопределил бы model/messages/tools); в защищённый набор входят и поля, устанавливаемые самим SDK (stream,stream_options), и намеренно опускаемые (temperatureу reasoning-моделей — опущение само по себе типизированное решение). Незанятые ключи (top_k,repetition_penalty,safe_prompt, provider-роутинг OpenRouter…) проходят насквозь дословно. Тесты: флаг true/false →max_completion_tokens/max_tokensв запросе,maxTokens<=0→ поле отсутствует; extra_body мерджится дословно;model/temperature/max_tokens/reasoning_effortиз extra_body не затирают типизированные;temperatureне «воскресает» на reasoning-модели; nil/пустая карта → no-op.