Skip to content

Разработка плагинов ​

Система плагинов CLIProxyAPI подключает к потоку хоста возможности моделей, учётных данных, планирования, преобразования, перехвата, наблюдения за использованием, расширения командной строки и management-страниц. Плагины запускаются как нативные динамические библиотеки внутри процесса CLIProxyAPI. Хост вызывает плагины через стабильный C ABI, а плагины могут вызывать callback хоста, чтобы переиспользовать существующие возможности CLIProxyAPI для HTTP, выполнения моделей, файлов учётных данных и логирования.

Область применения ​

Плагины подходят для следующих задач:

  • Предоставление списка моделей, разбора учётных данных, обновления входа и выполнения запросов для нового upstream.
  • Преобразование запросов, нормализация запросов, выбор планирования или перехват запросов перед upstream.
  • Преобразование ответов, нормализация ответов или перехват streaming chunk перед возвратом ответа клиенту.
  • Получение записей использования или добавление страниц и диагностических endpoints, принадлежащих только этому плагину, в management-часть.
  • Вызов существующего пути выполнения моделей хоста вместо копирования secret, proxy, логирования, статистики использования и логики маршрутизации в плагин.

Плагины не подходят для запуска недоверенного кода. Стандартные плагины как динамические библиотеки находятся в одном процессе с бинарником сервиса. Хост может восстановиться после части panic, но не может запретить плагину завершить процесс, повредить память, изменить глобальное состояние процесса или раскрыть чувствительные данные.

Документы возможностей ​

У каждой возможности есть отдельная страница. Материал собран по sdk/pluginapi/types.go, sdk/pluginabi/types.go, путям вызовов internal/pluginhost и примерам examples/plugin.

КатегорияВозможностьДокумент
Входная возможностьmodel_registrarРегистратор моделей
Входная возможностьmodel_providerПровайдер моделей
Входная возможностьauth_providerПровайдер учётных данных
Входная возможностьfrontend_auth_providerПровайдер фронтенд-аутентификации
Входная возможностьfrontend_auth_provider_exclusiveЭксклюзивный режим фронтенд-аутентификации
Входная возможностьschedulerПланировщик
Входная возможностьmodel_routerМаршрутизатор моделей
Входная возможностьexecutorИсполнитель
Обработка запросовrequest_translatorПреобразование запросов
Обработка запросовrequest_normalizerНормализация запросов
Обработка запросовrequest_interceptorПерехват запросов
Обработка ответовresponse_translatorПреобразование ответов
Обработка ответовresponse_before_translatorНормализация ответа перед преобразованием
Обработка ответовresponse_after_translatorНормализация ответа после преобразования
Обработка ответовresponse_interceptorПерехват ответов
Обработка ответовresponse_stream_interceptorПерехват стриминговых ответов
Расширениеthinking_applierThinking applier
Расширениеusage_pluginНаблюдение за использованием
Расширениеcommand_line_pluginРасширение командной строки
Расширениеmanagement_apiManagement API
Возможность хостаhost.*Callback хоста

Требования к запуску ​

Возможности плагинов требуют сборку с CGO. Ответы Management API содержат:

http
X-CPA-SUPPORT-PLUGIN: 1

1 означает, что текущий бинарник поддерживает плагины как динамические библиотеки, 0 — что не поддерживает. Этот заголовок показывает только возможность сборки. Он не означает, что плагины включены или что конкретный плагин загружен.

В конфигурации также нужно включить глобальный переключатель плагинов:

yaml
plugins:
  enabled: true
  dir: "plugins"
  configs: {}

Если plugins.enabled равен false, файлы плагинов и отдельная конфигурация плагинов могут существовать, но они не становятся фактически включёнными.

Обнаружение файлов плагинов ​

ID плагина берётся из имени файла динамической библиотеки без расширения. Например:

text
plugins/darwin/arm64/example-provider.dylib

Соответствующий ключ конфигурации:

yaml
plugins:
  configs:
    example-provider:
      enabled: true
      priority: 1

ID плагина должен соответствовать:

text
[A-Za-z0-9][A-Za-z0-9._-]{0,127}

Хост последовательно ищет по текущей платформе:

text
plugins/<GOOS>/<GOARCH>-<variant>
plugins/<GOOS>/<GOARCH>
plugins

macOS использует .dylib, Linux и FreeBSD используют .so, Windows использует .dll. Если один ID плагина найден в нескольких каталогах, срабатывает каталог с более высоким приоритетом.

Основы ABI ​

Каждый стандартный плагин как динамическая библиотека должен экспортировать:

c
int cliproxy_plugin_init(const cliproxy_host_api* host, cliproxy_plugin_api* plugin);

При инициализации плагин заполняет свою таблицу функций:

c
int call(char* method, uint8_t* request, size_t request_len, cliproxy_buffer* response);
void free_buffer(void* ptr, size_t len);
void shutdown(void);

Таблица функций, предоставленная хостом, нужна для обратных вызовов плагина в хост:

c
int call(void* host_ctx, char* method, uint8_t* request, size_t request_len, cliproxy_buffer* response);
void free_buffer(void* ptr, size_t len);

C ABI передаёт только имя метода, массив байтов и длину. Он не передаёт Go interface, Go slice, Go map, Go channel, context.Context или Go error. Запросы и ответы используют JSON-конверт, а raw byte поля автоматически представлены в JSON как base64.

Успешный ответ:

json
{
  "ok": true,
  "result": {}
}

Ответ с ошибкой:

json
{
  "ok": false,
  "error": {
    "code": "invalid_request",
    "message": "request is invalid"
  }
}

Жизненный цикл ​

Хост вызывает базовые методы:

МетодНаправлениеНазначение
plugin.registerХост вызывает плагинПервая загрузка плагина: чтение метаданных, полей конфигурации и объявлений возможностей.
plugin.reconfigureХост вызывает плагинПовторная передача конфигурации плагину после изменения конфигурации.
plugin.shutdownХост вызывает плагинОсвобождение ресурсов при выгрузке плагина или остановке хоста.

Запросы plugin.register и plugin.reconfigure содержат config_yaml. Он берётся из plugins.configs.<pluginID>. Хост сохраняет YAML-поля самого плагина и разбирает только принадлежащие хосту enabled и priority.

Ответ регистрации должен вернуть:

json
{
  "schema_version": 1,
  "metadata": {
    "Name": "example-provider",
    "Version": "0.1.0",
    "Author": "router-for-me",
    "GitHubRepository": "https://github.com/router-for-me/example-provider",
    "Logo": "https://example.com/logo.png",
    "ConfigFields": [
      {
        "Name": "mode",
        "Type": "enum",
        "EnumValues": ["safe", "fast"],
        "Description": "Execution mode."
      }
    ]
  },
  "capabilities": {
    "request_normalizer": true,
    "management_api": true
  }
}

ConfigFields используется management-частью для отображения конфигурации плагина. Это не заменяет собственную проверку конфигурации плагином. Плагин всё равно должен проверять важные для него поля в plugin.register или plugin.reconfigure.

Семантика конфигурации ​

Рекомендуемая минимальная конфигурация:

yaml
plugins:
  enabled: true
  dir: "plugins"
  configs:
    example-provider:
      enabled: true
      priority: 1
      mode: "safe"

Значения полей:

ПолеОписание
plugins.enabledГлобальный переключатель загрузки плагинов.
plugins.dirКаталог обнаружения плагинов, по умолчанию plugins.
plugins.store-sourcesСписок дополнительных URL registry магазина плагинов.
plugins.configs.<pluginID>.enabledПереключатель отдельного плагина. Если не указан, считается включённым.
plugins.configs.<pluginID>.priorityПорядок запуска, регистрации и маршрутизации плагинов. Плагины с более высоким приоритетом обрабатываются первыми.
Другие поляСобственная конфигурация плагина, хост сохраняет её как есть и передаёт плагину.

При обновлении конфигурации через Management API хост старается сохранить исходное YAML-дерево и изменить только запрошенные поля. После установки плагина из магазина хост записывает динамическую библиотеку и выставляет конфигурацию этого плагина в enabled: true, но не включает принудительно plugins.enabled.

Модель возможностей ​

Плагин объявляет реализованные возможности через capabilities. Частые возможности:

ВозможностьНаправление методаНазначение
Регистратор моделейmodel.registerРегистрирует статические метаданные моделей в хосте.
Провайдер моделейmodel.static / model.for_authПредоставляет статические модели или модели по записям учётных данных.
Провайдер учётных данныхauth.*Разбирает, логинит, опрашивает и обновляет учётные данные провайдера плагина.
Провайдер фронтенд-аутентификацииfrontend_auth.*Аутентифицирует клиентские запросы до обработки proxy.
Планировщикscheduler.pickВыбирает учётные данные из кандидатов или делегирует встроенному планировщику.
Маршрутизатор моделейmodel.routeМаршрутизирует подходящие запросы в исполнитель плагина, собственный исполнитель маршрутизатора или встроенный провайдер до выбора провайдера и учётных данных.
Исполнительexecutor.*Непосредственно выполняет upstream-запросы или stream-запросы.
Преобразование запросовrequest.translateПреобразует канонический запрос в upstream-протокол.
Нормализация запросовrequest.normalizeНормализует запросы перед входом в путь выполнения.
Перехват запросовrequest.intercept_before / request.intercept_afterИзменяет запросы выполнения до или после выбора учётных данных.
Преобразование ответовresponse.translateПреобразует канонический ответ в клиентский протокол.
Нормализация ответовresponse.normalize_before / response.normalize_afterНормализует ответы до или после встроенного преобразования.
Перехват ответовresponse.intercept_afterИзменяет нестриминговые ответы.
Перехват стриминговых ответовresponse.intercept_stream_chunkИзменяет chunk стримингового ответа.
Thinking applierthinking.applyПрименяет проверенную thinking-конфигурацию.
Наблюдение за использованиемusage.handleПолучает записи использования завершённых запросов.
Расширение командной строкиcommand_line.*Регистрирует и обрабатывает CLI-флаги плагина.
Management APImanagement.*Регистрирует management routes или браузерные ресурсы плагина.

Общее правило хоста: сначала встроенная логика, плагины закрывают недостающие участки. Если несколько плагинов могут обработать один этап, первыми выполняются плагины с более высоким приоритетом.

Callback хоста ​

Callback хоста — это вызовы плагина в хост, а не вызовы хоста в плагин. Они подходят для переиспользования proxy, учётных данных, маршрутизации моделей, логирования, статистики использования и управления ресурсами, которые уже обрабатывает хост.

Частые callback:

CallbackНазначение
host.http.doВыполняет один обычный HTTP-запрос через хост.
host.http.do_stream / host.http.stream_read / host.http.stream_closeВыполняет стриминговый HTTP-запрос через хост и читает или закрывает поток.
host.model.executeЗапускает нестриминговый запрос модели через путь выполнения моделей хоста.
host.model.execute_stream / host.model.stream_read / host.model.stream_closeЗапускает стриминговый запрос модели через путь выполнения моделей хоста и читает или закрывает поток.
host.stream.emit / host.stream.closeПозволяет executor plugin отправлять chunk в stream bridge хоста или закрывать поток.
host.logПишет через логгер хоста.
host.auth.listПеречисляет записи учётных данных хоста.
host.auth.getЧитает физический JSON-файл учётных данных.
host.auth.get_runtimeЧитает runtime-информацию об учётных данных.
host.auth.saveЗаписывает JSON учётных данных и обновляет runtime-запись.

Если плагин вызывает host.model.execute или host.model.execute_stream из management.handle или другого контекста, вызванного хостом, он должен переслать host_callback_id из запроса. Хост использует его, чтобы определить источник callback, и при вложенном выполнении модели пропускает request, response и stream interceptor того же плагина, предотвращая рекурсивный вызов самого себя. Другие включённые плагины всё ещё могут обработать вложенный запрос.

Для streaming callback рекомендуется явно вызывать соответствующий метод *_close. Хост может очистить часть ресурсов в конце области RPC, но явное закрытие быстрее освобождает потоковые ресурсы и упрощает поиск ошибок.

Management API и ресурсы плагинов ​

Плагин может объявлять два типа management-возможностей:

  1. Собственные API плагина, которым нужно управлять учётными данными.
  2. Ресурсные страницы плагина, которые браузер может открыть напрямую.

Границы маршрутов различаются:

ТипПоле регистрацииОткрытый путьАутентификация
Собственный Management API плагинаroutes/v0/management/...Требует management key.
Ресурсная страница плагинаresources/v0/resource/plugins/<pluginID>/...Сам resource request не проходит management-аутентификацию. В same-origin deployment Management Center доверенный JavaScript страницы может прочитать сохранённый management key и вызывать /v0/management/....

Пример: ID плагина example-provider, путь ресурса /status, итоговый адрес:

text
http://localhost:8317/v0/resource/plugins/example-provider/status

Плагин возвращает маршруты и ресурсы через management.register:

json
{
  "resources": [
    {
      "Path": "/status",
      "Menu": "Example Provider",
      "Description": "Show plugin status."
    }
  ],
  "routes": [
    {
      "Method": "POST",
      "Path": "/plugins/example-provider/run"
    }
  ]
}

Хост передаёт совпавшие запросы в management.handle. Запрос содержит method, path, headers, query и body; ответ содержит status code, headers и body.

Важно:

  • Собственные Management API маршруты плагина проверяются на конфликт с существующими маршрутами хоста /v0/management; конфликтующие маршруты плагина пропускаются.
  • Пути ресурсов плагина всегда монтируются под /v0/resource/plugins/<pluginID>/.
  • Устаревшие GET management routes с Menu обрабатываются как браузерные ресурсы и больше не раскрываются как management API.
  • Resource path не может содержать пробелы, :, * или ...
  • Установка и включение плагина с resource pages означает доверие браузерному коду этого плагина. В same-origin deployment этот код может читать localStorage Management Center, включая сохранённый management key, если он есть.
  • Чувствительные операции держите за маршрутами /v0/management/.... Resource page должна вызывать эти маршруты с сохранённым management key, а не выполнять чувствительную работу напрямую из неаутентифицированного resource GET-запроса.
  • Скрипты resource page должны поставляться вместе с плагином. Не загружайте сторонние скрипты на страницу, которая может получить доступ к same-origin management-контексту.

Management endpoints ​

Все следующие endpoints находятся под /v0/management и требуют management key.

Метод и путьНазначение
GET /pluginsПеречисляет найденные, настроенные и зарегистрированные плагины, возвращает plugins_enabled, effective_enabled, меню, метаданные и поля конфигурации.
PATCH /plugins/{pluginID}/enabledОбновляет только plugins.configs.<pluginID>.enabled, не изменяя глобальный plugins.enabled.
GET /plugins/{pluginID}/configПолучает сохранённый объект конфигурации плагина.
PUT /plugins/{pluginID}/configПолностью заменяет объект конфигурации плагина.
PATCH /plugins/{pluginID}/configПоверхностно объединяет объект конфигурации; значения null удаляют поля.
DELETE /plugins/{pluginID}Точечно выгружает целевой плагин, удаляет локальную динамическую библиотеку и сохранённую конфигурацию.
GET /plugin-storeПоказывает плагины в магазине и их локальный статус установки.
POST /plugin-store/{pluginID}/installУстанавливает или обновляет плагин из магазина; при нескольких источниках с одинаковым ID используйте ?source=<sourceID>.

Не смешивайте поля статуса из GET /plugins:

  • plugins_enabled: глобальный переключатель плагинов.
  • enabled: переключатель конфигурации отдельного плагина.
  • registered: динамическая библиотека плагина загружена и регистрация завершена.
  • effective_enabled: фактическое включённое состояние, когда одновременно выполнены глобальный переключатель, переключатель плагина и состояние регистрации.

При установке или обновлении плагина хост сначала скачивает release asset и проверяет checksums.txt, затем точечно выгружает целевой плагин перед перезаписью динамической библиотеки, записывает новый файл и запускает hot reload конфигурации. Если платформа или блокировка файла не позволяют перезаписать уже загруженную динамическую библиотеку, endpoint возвращает conflict response, требующий перезапуска.

Формат публикации магазина плагинов ​

Registry магазина плагинов по умолчанию:

text
https://raw.githubusercontent.com/router-for-me/CLIProxyAPI-Plugins-Store/main/registry.json

Можно добавить сторонние источники в конфигурации:

yaml
plugins:
  store-sources:
    - "https://example.com/cliproxyapi-plugins/registry.json"

Формат registry:

json
{
  "schema_version": 1,
  "plugins": [
    {
      "id": "example-provider",
      "name": "Example Provider",
      "description": "Example plugin provider.",
      "author": "router-for-me",
      "version": "0.1.0",
      "repository": "https://github.com/router-for-me/example-provider",
      "logo": "https://example.com/logo.png",
      "homepage": "https://example.com",
      "license": "MIT",
      "tags": ["provider"]
    }
  ]
}

Требования:

  • schema_version должен быть 1.
  • id, name, description, author, repository обязательны.
  • repository должен быть https://github.com/{owner}/{repo}.
  • version — fallback для отображения. Фактическая устанавливаемая версия берётся из GitHub latest release tag. Tag может начинаться с v; хост удаляет ведущий v перед проверкой версии.

Release плагина должен предоставлять zip asset для текущей платформы и checksums.txt:

text
<pluginID>_<version>_<goos>_<goarch>.zip
checksums.txt

Корень zip должен напрямую содержать целевую динамическую библиотеку:

text
example-provider.dylib

Не помещайте динамическую библиотеку в подкаталог. checksums.txt использует обычный формат sha256:

text
<sha256>  example-provider_0.1.0_darwin_arm64.zip

Советы по разработке ​

Рекомендуется начинать с примеров в репозитории:

bash
make -C examples/plugin list
make -C examples/plugin build

Частые примеры:

ПримерФокус
examples/plugin/simpleПолные ABI skeleton на Go, C и Rust.
examples/plugin/codex-service-tierПлагин нормализации запросов.
examples/plugin/schedulerПлагин планировщика.
examples/plugin/claude-web-search-routerПлагин ModelRouter, который маршрутизирует запросы Claude Code web_search во встроенные провайдеры или собственный исполнитель.
examples/plugin/management-apiСобственные management routes и resource pages плагина.
examples/plugin/host-callback-auth-filesВызов callback файлов учётных данных хоста.
examples/plugin/host-model-callbackВызов callback выполнения моделей хоста и демонстрация защиты от рекурсии.

При разработке:

  • Объявляйте только те возможности, которые плагин действительно реализует.
  • Собственные HTTP-запросы плагина лучше выполнять через host.http.*, чтобы не обходить proxy, логирование и транспортную политику хоста.
  • Когда нужен запрос модели, предпочитайте host.model.*; не копируйте учётные данные хоста в плагин.
  • Явно закрывайте streaming resources после использования.
  • Не раскрывайте host callbacks, которые читают учётные данные, записывают учётные данные или выполняют привилегированные действия, напрямую через неаутентифицированные query parameters ресурса. Если пользовательский UI должен запускать такие действия, используйте same-origin доверенную resource page и аутентифицированный вызов /v0/management/....
  • Сохраняйте обратную совместимость собственных полей конфигурации плагина; при удалении полей поддерживайте старую конфигурацию.
  • Не логируйте secret, token, исходный JSON учётных данных или чувствительные тела пользовательских запросов.
  • После изменения динамической библиотеки используйте plugin management API или перезапустите сервис, чтобы старый экземпляр плагина больше не использовался.

Минимальный процесс проверки ​

После разработки локального плагина можно проверить его так:

  1. Соберите динамическую библиотеку для текущей платформы и поместите её в plugins/<GOOS>/<GOARCH>/ или plugins/.
  2. Включите plugins.enabled в config.yaml и добавьте plugins.configs.<pluginID>.
  3. Запустите CLIProxyAPI.
  4. Выполните GET /v0/management/plugins и убедитесь, что registered: true и effective_enabled: true.
  5. Если у плагина есть resource pages, откройте /v0/resource/plugins/<pluginID>/<path>.
  6. Если у плагина есть Management API, запросите соответствующий маршрут /v0/management/... с management key.
  7. После изменения плагина установите или удалите его через management API либо перезапустите сервис и убедитесь, что старая динамическая библиотека больше не используется.

Лицензия MIT.