Перейти к содержанию

ADR 0083: settings.toml — дискриминант ai.mode и вложенные секции (local / acp / mcp_only / cloud)

Статус: Accepted · Implemented
Дата: 2026-04-20

Связанные ADR

ADR Роль
0028 путь и секреты
0038 фасад провайдеров и инструментов
0048 ACP, MCP в сессии
0016 ACP как внешний агент
0081 параметрические Intent Melody :start:end для редактора — смежная тема, не часть ai.mode
## Резюме
  • Секция [ai] в settings.toml: mode = local | acp | mcp_only | cloud.
  • Вложенные секции; без обратной совместимости со старым provider.


Контекст

Секция [ai] в пользовательском TOML сейчас смешивает несколько независимых осей:

  • Какой контур отвечает за текст ассистента (локальный Ollama, облачный API, Cursor ACP).
  • Флаг «не звать встроенный LLM, ждать ответ только из MCP» (chat_mcp_only) — по смыслу это отдельный режим взаимодействия, а не «ещё один провайдер».

Читаемость файла и документации страдает: приходится держать в голове комбинации provider + булевых флагов.


Решение

1. Дискриминант верхнего уровня

В [ai] вводится обязательное (для канона) поле:

TOML-ключ Смысл
mode Один из: local, acp, mcp_only, cloud.

Почему не голое mcp: слово совпадает с протоколом и подключёнными MCP-серверами (инструменты в сессии ACP, внешние stdio-серверы и т.д.). Значение режима чата должно читаться как источник ответа ассистента, а не как «MCP включён». Каноническое имя режима — mcp_only: встроенный LLM после отправки пользователя не вызывается; текст ассистента приходит только из контура внешнего MCP (например send_chat с ролью assistant). Семантически то же выражали бы assistant_via_mcp или no_builtin_provider; в TOML выбираем одно короткое mcp_only, синонимы в коде/доках не обязательны.

Семантика:

Значение Назначение
local Встроенный локальный провайдер: [ai.local] с backend = "ollama" (и далее другие значения) плюс [ai.local.ollama] с полями модели/endpoint. Вложенная таблица даёт короткие ключи на листе (model, base_url). Другие локальные бэкенды — отдельные backend и [ai.local.<name>].
acp Чат через Cursor Agent / ACP (cursor-agent): пути, model_id, политика подмешивания MCP-серверов в сессию — в подсекции ACP; это не то же самое, что режим mcp_only.
mcp_only Режим только ответа через MCP для текста ассистента: встроенный провайдер (Ollama/облако) в этом смысле не используется. Подключённые MCP-сервера как инструменты могут существовать и в local / acp / cloud — это ортогонально.
cloud Облачные провайдеры с API-ключами (Anthropic, OpenAI-совместимые, DeepSeek и т.д.): выбор провайдера и учётные данные — во вложенных таблицах.

2. Вложенные таблицы (направление)

Ключи и точная вложенность фиксируются при реализации; нормативный скелет:

[ai]
mode = "local" # local | acp | mcp_only | cloud

# Поля, общие для любого режима (пример): размещение UI настроек, флаги истории — по мере необходимости.

# Локальный режим: явный backend и вложенная таблица с тем же именем (v1 — ollama).
[ai.local]
backend = "ollama" # ollama | openai_compatible | … (канон значений фиксируется при реализации)

[ai.local.ollama]
# model — идентификатор модели в API Ollama; base_url, request_timeout — при необходимости.

[ai.acp]
# cursor_acp_path, cursor_acp_model_id, политика acp_auto_inject_ide_mcp (подмешивание MCP-серверов в сессию агента).

[ai.mcp_only]
# Лимиты/флаги, специфичные для режима «без встроенного LLM, ответ ассистента только из MCP».

[ai.cloud]
# Поле active_provider = "anthropic" | "openai" | "deepseek" (или иной канон списка).

# Предпочтительно: фиксированные вложенные таблицы по провайдеру (не массив [[...]]):
[ai.cloud.anthropic]
# model, base_url (если отличается), ссылки на ключ в ai-keys.toml — при реализации.

[ai.cloud.openai]

[ai.cloud.deepseek]

Плагинируемый каталог облачных провайдеров (массив [[...]], динамическая загрузка и т.п.) не входит в scope этого ADR и отложена; v1 — только фиксированный набор вложенных таблиц выше.

Именование: в TOML — snake_case, согласовано с CascadeTomlSerializer / 0028. Вложенность [ai.local.ollama] / [ai.cloud.*] задаёт контекст: на листе достаточно model, base_url, без длинных префиксов в имени ключа. Для выбора локального сервиса/API в [ai.local] каноническое поле — backend; синоним engine не используем (путается с рантаймом инференса и с model).

Локальный режим: «тип» vs модель. Поле вроде model_type в смысле формата весов (GGUF, SafeTensors, …) в пользовательский settings.toml не выносим — это не контракт IDE. Нужны два уровня:

  1. backend в [ai.local] — строковый дискриминант: ollama, позже openai_compatible и т.д. Должен совпадать с активной подтаблицей [ai.local.<backend>] (например backend = "ollama"[ai.local.ollama]). Так читаемость в корне локальной секции не зависит только от пути; вложенная таблица по-прежнему несёт короткие ключи (model, …).
  2. model — строковый идентификатор модели в API выбранного движка (например qwen2.5-coder:7b для Ollama), не путать с «типом файла модели».

Что кроме Ollama (продуктово, не обязательно v1): тот же абстрактный OpenAI-compatible HTTP (как у облака), но с base_url на localhost — типично LM Studio, llama.cpp в режиме совместимости с OpenAI API, vLLM, LocalAI и т.д. В схеме это отдельная подтаблица, например [ai.local.openai_compatible] с model, base_url, при необходимости ссылкой на ключ в ai-keys.toml (редко для локали). Реализация и тесты — после v1/Ollama; плагинируемый реестр движков — по-прежнему отложен.

3. Секреты

По-прежнему не хранить ключи API в settings.toml: отдельный ai-keys.toml (см. 0028). В ADR только какие поля ссылаются на ключи по имени/слоту.


Обратная совместимость

Не требуется. Старые ключи вида provider, chat_mcp_only, плоские default_ollama_model, облачные URL в корне [ai] снимаются с канона: миграция — ручная правка файла и обновление сэмплов в репозитории (docs/samples/settings*.toml). При желании в коде допускается одноразовый скрипт/док «как переписать старый файл» вне обязательной автомиграции в рантайме.


Последствия

  1. AiSettings и связанный VM перестраиваются под дискриминант + вложенные типы; сериализация Tomlyn должна воспроизводить вложенные таблицы предсказуемо.
  2. UI настроек AI (ComboBox провайдера и т.д.) маппится на mode и подсекции вместо плоского списка Ollama|…|CursorACP.
  3. Документация и сэмплы — единый пример с [ai].mode и вложенными блоками.
  4. Тесты — контракт десериализации TOML для новой формы; удаление тестов на старые ключи.

Отклонённые / отложенные альтернативы

  • Оставить только provider + флаги — отклонено как менее читаемое для пользователя и для ADR.
  • Режим с именем mcp — отклонено: неоднозначно с MCP как протоколом и списком подключённых серверов; канон — mcp_only (см. пояснение выше; assistant_via_mcp / no_builtin_provider — смысловые синонимы, не отдельные значения в TOML).
  • Автомиграция при загрузке — не делаем по требованию; снижает сложность кода и скрытые сюрпризы.
  • Плагины и расширяемый реестр облачных провайдеров — отложено; канон v1 — фиксированные [ai.cloud.anthropic] / openai / deepseek (см. скелет выше).

Статус реализации

Реализовано в коде (модели, сериализация Tomlyn, VM, панель «AI и чата», сэмплы). Расширения (например openai_compatible в [ai.local], редактирование облачных моделей из UI) — по мере необходимости.