Расширения OpenCode v2: устройство и интеграция

Дата исследования: 2026-09-13. Статья описывает общие возможности плагинов OpenCode v2: типы, загрузку, регистрацию возможностей, хуки выполнения, границы разрешений и жизненный цикл. Материал предназначен для разработки плагинов и встраиваемой интеграции.

Исследование основано на ветке v2 изученного репозитория OpenCode, коммит 2308db16387c9b59e88d732a93ec9bac54462b03. Ссылки на исходники используют пути относительно репозитория в этом фиксированном коммите. Описанные интерфейсы и поведение относятся к данной версии и не обещают совместимости с другими версиями OpenCode или прежним API плагинов.

Это исследование исходного кода и описание возможностей. Примеры поясняют интерфейсы и вызовы; независимо они не запускались и не проверялись.

Plugin: точка входа для расширений

Что такое Plugin

Расширения OpenCode подключаются к существующему выполнению через регистрацию возможностей и хуков. В текущей реализации OpenCode v2 понятие Extension в основном соответствует механизму Plugin. Плагин может предоставлять ассистентов, инструменты, навыки, модели, команды и другие возможности, а также менять поведение в заданных точках выполнения. Сеансы, запросы к моделям, результаты инструментов и записи выполнения по-прежнему обрабатывает ядро OpenCode.

После загрузки плагин регистрирует возможности. Сеанс подготавливает контекст и вызывает модель через хук запроса. Вызовы инструментов сохраняют результаты через хуки выполнения и возвращают их модели; окончательный ответ завершает выполнение.
Процесс Plugin: от загрузки и регистрации возможностей до выполнения сеанса
Показать исходный код диаграммы Mermaid
flowchart TD
    A[配置文件、本地插件或 SDK 注册] --> B[插件加载与生命周期管理]
    B --> C[构建助手、工具、技能等能力]
    C --> D[会话选择助手]
    D --> E[准备上下文与工具快照]
    E --> F[请求阶段钩子]
    F --> G[调用模型]
    G --> H{返回内容}
    H -->|工具调用| I[工具执行与钩子]
    I --> J[保存结果与执行事件]
    J --> G
    H -->|最终回答| K[本次执行结束]

Виды Plugin

Внутренние, внешние и SDK-плагины различаются источником подключения, но управляются единой системой выполнения. Все три вида могут регистрировать ассистентов, инструменты и хуки. Различия в основном заключаются в том, кто предоставляет определение, как оно загружается в хост и внедряет ли хост дополнительные внутренние сервисы ядра. Здесь рассматриваются серверные плагины; TUI-плагины терминального интерфейса используют другую точку расширения.

Параметр сравнения Внутренний плагин Внешний плагин SDK-плагин
Поставщик Исходный код и дистрибутив OpenCode Сопровождающие проекта или отдельного пакета плагина Приложение-хост, встраивающее OpenCode
Точка входа PluginInternal : наборы pre / post (наборы) Обнаружение в каталогах, файлы или пакеты в конфигурации Во встраиваемом хосте: opencode.plugin(definition)
Способ загрузки Ядро напрямую импортирует определение Разрешение модуля и чтение экспорта по умолчанию Передача объекта плагина непосредственно в памяти
Доступные интерфейсы Публичный Context и внутренние сервисы, внедрённые хостом Публичный Plugin Context Публичный Plugin Context; через замыкание доступны зависимости, явно переданные хостом
Основной способ настройки Нативная конфигурация, внутренние сервисы или встроенные значения по умолчанию plugins[].optionsctx.options Код хоста и замыкания; отдельного параметра options у интерфейса регистрации нет
Источник обновлений Версия кода OpenCode; некоторые плагины самостоятельно отслеживают конфигурацию Изменения конфигурации, поддерживаемых локальных точек входа или настроек пакета Повторная регистрация хостом с увеличением внутренней ревизии
Область экземпляра Независимая активация для каждого Location Отдельная активация по конфигурации каждого Location Общие определения в одном хосте, независимая активация в каждом Location
Применение Реализация нативных ассистентов, инструментов, провайдеров и обработки конфигурации Добавление проектных или бизнес-возможностей в существующий сервис OpenCode Встраивание OpenCode в собственное приложение JS/TS и управление им

Effect и Promise — два интерфейса написания плагинов. Объект Effect-плагина с публичным интерфейсом можно экспортировать по умолчанию для загрузки через конфигурацию или напрямую зарегистрировать во встраиваемом SDK. Основные возможности переиспользуются, но порядок загрузки и способ передачи конфигурации меняются.

Внутренние плагины оформляют часть собственных функций OpenCode в виде плагинов. Это не специальные файлы, устанавливаемые пользователем в каталог. Они статически импортируются исходным кодом ядра и явно включаются в наборы PluginInternal .PluginInternal.list() получает сервисы текущего Location и через Effect.provide(context) внедряет их во внутренние плагины, после чего передаёт их единому загрузчику.

Здесь существуют два уровня Context: effect(ctx) по-прежнему принимает публичный Plugin Context, а внутренний плагин дополнительно получает через среду Effect сервисы Config.Service, Permission.Service, Shell.Service, Location.Service и другие. Дополнительные возможности обусловлены явно внедрёнными зависимостями, а не тем, что ID плагина начинается с opencode. .

Пример внутреннего плагина Набор Фактическая ответственность
opencode.agent pre Регистрация базовых определений нативных ассистентов
opencode.tool.shell pre Регистрация инструмента Shell с нативным выполнением, разрешениями и сервисами состояния
Нативные провайдеры, поиск и другие плагины инструментов pre Базовые возможности моделей, поиска и инструментов
opencode.config.agent post Чтение конфигурации ассистентов и связанных файлов Markdown с применением к списку ассистентов
Плагины конфигурации провайдеров, навыков, политик и вариантов моделей post Применение конфигурации или последующей обработки к ранее предоставленным возможностям

Это объясняет связь пользовательских ассистентов с внутренними плагинами. Пользователь редактирует конфигурацию ассистента, а чтением, отслеживанием и применением занимается opencode.config.agent. Каждый пользовательский ассистент — определение Agent; отдельный плагин для него не требуется. Внешние и SDK-плагины тоже могут регистрировать Agent, которые затем корректируются конфигурационным плагином.

Встроенные плагины также участвуют в выборе включения и отключения. Например, -opencode.config.agent исключает из активного набора плагин применения конфигурации ассистентов, затрагивая соответствующие настройки. Решение о сохранении встроенного плагина нужно принимать с учётом его фактической роли.

Для добавления нативных функций, зависящих от приватных сервисов Core, обычно требуется изменить исходники OpenCode и добавить плагин во встроенный набор. Новые сервисные зависимости требуют корректировки сборки сервисов. Такие изменения собираются, выпускаются и обновляются вместе с OpenCode. Внутренние плагины подходят для функций самого движка; обычные бизнес-инструменты предпочтительно подключать через публичные интерфейсы.

Основания: набор внутренних плагинов и внедрение сервисов (packages/core/src/plugin/internal.ts), плагин нативных ассистентов (packages/core/src/plugin/agent.ts), плагин конфигурации ассистентов (packages/core/src/config/plugin/agent.ts), плагин инструмента Shell (packages/core/src/tool/plugin/shell.ts).

Внешние плагины добавляют возможности существующему сервису OpenCode через файлы или пакеты. «Внешний» означает, что код не входит во встроенный набор. При выполнении он всё равно загружается в процесс OpenCode. Загрузчик предоставляет публичный Plugin Context, но не внедряет дополнительные сервисы Core, как для внутренних плагинов. Код может пользоваться файлами, сетью и другими возможностями среды выполнения; это не отдельный процесс и не защищённая песочница.

У внешних плагинов два пути обнаружения:

  • Автоматическое обнаружение: сканируются plugin/ и plugins/ в нативных каталогах конфигурации. Текущая реализация напрямую распознаёт файлы .ts, .js , а также поддерживает каталоги пакетов и подходящие символические ссылки. В каталоге пакета проверяются строковые поля файла package.json : exports, module, main, затем index.ts, index.js. Эту простую логику нельзя считать поддержкой всех сложных правил экспорта пакетов.
  • Явная конфигурация:plugins принимает относительные и абсолютные пути, файловые URL и разрешимые пакеты. Относительные пути разрешаются от каталога файла конфигурации. Локальные пути передаются загрузчику модулей, пакеты — механизму разрешения пакетов, который пробует подраздел server или корневую точку входа пакета.

Результаты автоматического обнаружения сначала добавляются в список операций, затем применяется явная конфигурация. Поэтому автоматически найденный плагин можно отключить в настройках. Автообнаружение напрямую не перечисляет файлы .mjs ; для такой точки входа путь задаётся явно, а загрузку выполняет среда исполнения.

{
  "plugins": [
    {
      "package": "./plugins/example.ts",
      "options": {
        "serviceUrl": "https://api.example.com"
      }
    }
  ]
}

Приведённые пути иллюстрируют формат конфигурации. Плагин через ctx.options читает serviceUrl и самостоятельно проверяет обязательные поля, диапазоны значений и ограничения адресов. Сам факт передачи объекта не означает валидацию бизнес-настроек.

Внешний модуль должен по умолчанию экспортировать объект плагина с id + effect или id + setup :

Способ написания Точка инициализации Подключение и очистка
Effect effect(ctx) Хост выполняет плагин внутри его Scope; ресурсы связываются со scoped-жизненным циклом или finalizer
Promise setup(ctx) Загрузчик через fromPromise преобразует определение в Effect-плагин; можно вернуть функцию cleanup

@opencode-ai/plugin/effect предоставляет Effect API, а корневой вход пакета @opencode-ai/plugin — Promise API текущей ветки. У прежнего API отдельная точка входа v1 ; принадлежность к плагинам OpenCode сама по себе не гарантирует совместимость интерфейсов.

Типичная цепочка загрузки внешнего плагина:

配置目录 / plugins 配置
  → ConfigPluginSource 生成有序操作与本地文件时间戳
  → PluginSupervisor 解析路径或包、导入模块、校验默认导出
  → 适配 Promise 定义,并注入该来源的 options
  → Plugin.Service 创建 Location 内的插件实例
  → 注册助手、工具、钩子及需要清理的资源

Изменения файлов или конфигурации могут запустить пересоздание набора плагинов в пределах поддерживаемого обнаружения и наблюдения. Произвольный файл зависимости не обязательно обновляется в реальном времени. В частности, при явном указании каталога в качестве точки входа текущая реализация не гарантирует перезагрузку при изменении файлов внутри него. Обновления версий пакетов также следует проводить явным развёртыванием или изменением конфигурации.

Ошибка импорта модуля или проверки экспорта записывается как предупреждение загрузки; соответствующий источник пропускается. Ошибку инициализации уже на этапе активации обрабатывает единое управление плагинами. При диагностике отдельно проверяйте обнаружение файла, разрешение модуля, активацию плагина и регистрацию возможностей. Наличие настройки само по себе не доказывает доступность инструмента.

Основания: обнаружение источников и локальное наблюдение (packages/core/src/config/plugin/source.ts), загрузка модулей и внедрение конфигурации (packages/core/src/plugin/supervisor.ts), публичные точки входа пакета (packages/plugin/package.json), адаптер Promise (packages/plugin/src/promise/adapter.ts).

SDK-плагины напрямую регистрируются встраиваемым хостом и подходят для использования OpenCode как внутреннего движка приложения. Под SDK здесь понимается именно встраиваемый хост текущей ветки @opencode-ai/sdk-next .OpenCode.create() создаёт среду выполнения и цепочку вызовов HTTP-маршрутов в памяти процесса приложения. Для этой внутренней цепочки не требуется сетевое прослушивание; инструменты, модели и бизнес-сервисы по-прежнему могут выполнять собственные сетевые запросы.

Хост через opencode.plugin(definition) напрямую передаёт объект Effect-плагина, минуя обнаружение внешних модулей, разрешение пакета и проверку экспорта по умолчанию. Это не загрузка кода в уже работающий удалённый сервис OpenCode. Обычный HTTP-клиент и ctx.plugin.list() из Plugin Context также не являются этой встраиваемой точкой регистрации.

Пример ниже показывает регистрацию и запрос плагина во встраиваемом хосте. Версии зависимостей должны соответствовать исследованной версии:

import { AbsolutePath, Location, OpenCode } from "@opencode-ai/sdk-next"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const opencode = yield* OpenCode.create()

  yield* opencode.plugin({
    id: "example.reviewer",
    effect: (ctx) =>
      ctx.agent.transform((draft) => {
        draft.update("reviewer", (agent) => {
          agent.description = "检查代码并给出修改建议"
          agent.system = "分析代码质量,并说明建议的依据。"
          agent.mode = "primary"
        })
      }).pipe(Effect.asVoid),
  })

  const location = Location.Ref.make({
    directory: AbsolutePath.make(process.cwd()),
  })
  return yield* opencode.plugin.list({ location })
})

const result = await Effect.runPromise(program.pipe(Effect.scoped))
console.log(result.data)

Ассистент в примере определяет только назначение и промпт; разрешения настраиваются отдельно. После примера закрывается Scope, владеющий хостом. В реальном встраиваемом приложении этот Scope должен охватывать весь жизненный цикл сервиса.

Область действия и обновления SDK-плагина следует рассматривать на двух уровнях:

Уровень Управляемые данные Область действия
Реестр хоста Map<plugin.id, Versioned>: хранение определения и возрастающей ревизии Общий для одного встраиваемого хоста; у разных хостов отдельные реестры
Активный набор Location Экземпляры плагинов, Transform, Hook и Scope в контексте данного каталога Отдельная активация и очистка для каждого Location

Каждая регистрация публикует sdk.plugin.updated. Уже работающие Location при обновлении пересоздают набор плагинов. Новые Location и повторно запущенные после освобождения читают текущее определение из реестра хоста. Поэтому одна регистрация хостом не означает однократную инициализацию. Изменяемые объекты в замыкании могут совместно использоваться несколькими экземплярами Location одного хоста; их нельзя автоматически считать состоянием отдельного сеанса.

Повторная передача того же ID в один SDK-реестр заменяет определение и создаёт новую ревизию. Разные хосты друг друга не перезаписывают. Возврат из регистрации означает лишь запись определения и публикацию обновления, а не завершение активации во всех Location. Для проверки нужно запросить фактические списки плагинов и возможностей целевого Location и при необходимости дождаться события активации.

Текущий SDK-реестр предоставляет только register и all; отдельного интерфейса unregister нет. Конфигурация каждого Location может отключить активацию по ID, но не удаляет определение из реестра хоста. Закрытие хоста очищает ресурсы выполнения. После его пересоздания плагины нужно регистрировать заново: прежняя регистрация в памяти не является постоянной записью об установке.

SDK принимает Effect-плагины. Автоматическая адаптация Promise-плагинов внешним загрузчиком здесь не выполняется. Для повторного использования Promise-определения нужно явно применить соответствующий адаптер fromPromise . SDK-путь не внедряет дополнительные options ; публичный Context по умолчанию предоставляет пустой объект. Собственные настройки и бизнес-клиенты хост обычно передаёт фабричной функцией или замыканием.

SDK-плагин также не получает автоматически среду сервисов Core внутренних плагинов. Хост может явно передать зависимости, но прямое использование приватных сервисов Core усиливает привязку к версии. В исследованном коде sdk-next всё ещё является переходным пакетом с private: true , поэтому пример не обещает доступности опубликованного стабильного npm API для установки.

Основания: точка входа встраиваемого хоста (packages/sdk-next/src/opencode.ts), SDK-реестр (packages/core/src/plugin/sdk.ts), состояние SDK-пакета (packages/sdk-next/package.json), публичный хост плагинов (packages/core/src/plugin/host.ts). Исходники встраиваемых тестов (packages/sdk-next/test/embedded.test.ts) содержат сценарии обновления между Location, повторной активации после освобождения Location и изоляции хостов. Эти сценарии изучены, но тесты независимо не запускались.

Три источника объединяются в один упорядоченный набор активации. После определения включённых элементов текущий порядок таков:

内部 pre → SDK 注册插件 → 外部文件 / 包插件 → 内部 post

ConfigPluginSource отвечает за внешние источники и операции конфигурации; PluginSupervisor объединяет их с внутренними и SDK-определениями; Plugin.Service выполняет окончательную проверку повторяющихся ID, управление Scope, инициализацию и замену. Три источника не следует представлять как три независимых движка.

Из-за такого порядка возможности ассистентов, предоставленные внешними и SDK-плагинами, могут быть изменены последующими конфигурационными плагинами. Для определения окончательного промпта, модели или разрешений нужно читать итоговый список возможностей, а не только начальное определение отдельного плагина.

ID плагина, имя пакета и путь точки входа выполняют разные роли. Например, если конфигурация загружает файл, экспортирующий плагин с ID example.reviewer , для отключения нужно указать -example.reviewer. Селекторы поддерживают точный ID, prefix.* и *. Операции конфигурации выполняются последовательно, и последующая операция может повторно включить существующее определение.

Важно различать два правила совпадения имён:

  • Повторная регистрация одного ID в одном SDK-реестре: обновляет соответствующее определение; реестр поддерживает такую замену.
  • Один и тот же ID из разных действующих источников: этап активации отклоняет весь текущий набор из-за дублирования ID, а не молча перезаписывает по приоритету источника. Не передавайте одно определение одновременно внешним файлом и через SDK.

Кроме того, текущий Plugin.Service пропускает активацию, если ID и версии всего упорядоченного набора полностью совпадают. При любом изменении набора он обходит новый набор, очищает и повторно инициализирует уже существующие плагины. Обновление одного источника может переинициализировать и неизменённые плагины. Все три вида должны правильно освобождать ресурсы и не считать инициализацию однократным бизнес-действием. При неудачной замене предпринимается восстановление старой версии, но уже возникшие внешние бизнес-эффекты откатить нельзя.

Основания: объединение источников и порядок включения/отключения (packages/core/src/plugin/supervisor.ts), единая активация и замена (packages/core/src/plugin.ts), исходники тестов конфигурации и порядка загрузки (packages/core/test/config/plugin.test.ts).

Первая основная часть Plugin: Transform

Transform объявляет возможности, Hook обрабатывает конкретное выполнение. Выбирая точку расширения, сначала определите, к какой области относится задача.

Задача Нативный интерфейс расширения
Зарегистрировать, изменить или удалить ассистента ctx.agent.transform
Зарегистрировать исполняемый инструмент ctx.tool.transform
Предоставить описание навыка и точки доступа к ресурсам ctx.skill.transform
Добавить команду ctx.command.transform
Изменить каталог провайдеров и моделей и выбор по умолчанию ctx.catalog.transform
Предоставить источники справочных материалов ctx.reference.transform
Настроить аутентификацию и подключение ctx.integration.transform
Предоставить поисковый бэкенд ctx.websearch.transform
Изменить контекст и видимые инструменты текущего запроса ctx.session.hook("context", ...)
Проверить вход инструмента или обработать результат ctx.tool.hook(...)
Изменить SDK модели или её экземпляр ctx.aisdk.hook(...)
Изменить HTTP-запрос или ответ модели ctx.session.hook("http.request" / "http.response", ...)
Изменить параметры создания команды ctx.shell.hook("create.before", ...)
Наблюдать события в реальном времени ctx.event.subscribe()
Создавать сеанс, отправлять ему данные, ожидать или прерывать его ctx.session.*

Области состояния Agent, Catalog, Command, Integration, Reference, Skill и другие сохраняют активные Transform и последовательно пересоздают состояние из базового. При выгрузке плагина его Transform удаляется, затем результат строится заново. Transform должен изменять только Draft текущего вызова, не сохраняя ссылку на него и не создавая внешних бизнес-эффектов при пересборке. Внешние данные следует сначала загрузить и сохранить, а затем вызвать у соответствующей области reload().

В основе Tool — регистрация инструментов, связанная со Scope, и снимки для запросов. Жизненный цикл имеет ту же принадлежность, что и перечисленные области, но нельзя заключать, что все transform используют полностью одинаковые контейнеры состояния или возвращаемые типы.

Вторая основная часть Plugin: Hook

Hook — точка расширения, предусмотренная хостом в процессе выполнения. Плагин регистрирует callback в точке расширения. Когда OpenCode достигает соответствующего узла, он автоматически вызывает callback и передаёт контекст узла. В рамках контракта интерфейса плагин может читать данные, менять параметры или обрабатывать результаты, участвуя в запросах модели, выполнении инструментов и других процессах.

Работа Hook делится на регистрацию и срабатывание. При инициализации плагин через ctx.session.hook(...), ctx.tool.hook(...) и другие интерфейсы объявляет нужные узлы. Хост вызывает callback только при достижении узла во время выполнения. После одной регистрации callback может срабатывать многократно при повторных прохождениях узла. Хост ждёт завершения callback и продолжает согласно контракту; например, tool.execute.before позволяет через Tool.Error отклонить выполнение инструмента.

Transform формирует доступные возможности, а Hook изменяет их поведение в конкретных узлах. Например, инструмент регистрируют через ctx.tool.transform; вход отдельного вызова проверяют через ctx.tool.hook("execute.before", ...); контекст, который отправится модели, меняют через ctx.session.hook("context", ...). Hook входит в текущую цепочку вызовов, а ctx.event.subscribe() служит для подписки на события и наблюдения выполнения.

Хуки выполнения вызываются последовательно в порядке регистрации. Поздний Hook видит изменения ранних. Если несколько плагинов меняют одно поле, проверяйте окончательный порядок загрузки. Встроенные плагины разделены на предшествующий и последующий наборы; нельзя считать, что внешние всегда перезаписывают последними.

Hook Что можно изменять или наблюдать Замечания
session.context Системный промпт, сообщения и определения инструментов текущего запроса ID ассистента и модели задают идентичность контекста; изменение сообщений влияет только на текущий запрос и не означает добавления в постоянную историю
tool.execute.before Входные данные инструмента Можно вернуть Tool.Error для запрета выполнения
tool.execute.after Успешный результат или ошибка инструмента Обработка результата и добавление информации
session.http.request/response HTTP-запрос и ответ модели Возможен доступ к учётным данным и полному вводу; содержимое журналов нужно контролировать
aisdk.sdk/language SDK и фактический экземпляр модели Подходит для адаптации провайдера
shell.create.before Команда, каталог, лимит времени, Shell и переменные окружения Строковые правила сами по себе не обеспечивают системную изоляцию

Среди текущих публичных типов Hook только tool.execute.before объявляет восстанавливаемый канал ошибки Tool.Error . Остальные Hook нельзя считать универсальным middleware для произвольного выбрасывания бизнес-ошибок.

Основания: Plugin Context (packages/plugin/src/effect/plugin.ts), пересборка состояния (packages/core/src/state.ts), регистрация и выполнение Hook (packages/core/src/plugin/hooks.ts), регистрация инструментов и снимки (packages/core/src/tool.ts).

Примеры инструментов и жизненный цикл

Пример 1: определение Tool через Plugin Transform

Инструмент содержит определение, видимое модели, и функцию, исполняемую хостом. После того как модель создаёт имя и вход инструмента, OpenCode обрабатывает вызов с возможностями, зафиксированными для текущего запроса. Функция инструмента получает предоставленные хостом sessionID, agent, messageID и данные вызова id; длительная операция может сообщать ход выполнения через context.progress() .

Ниже приведён самостоятельный пример инструмента с Effect API:

import { Plugin } from "@opencode-ai/plugin/effect"
import { Effect, Schema } from "effect"

export default Plugin.define({
  id: "example.echo",
  effect: Effect.fn(function* (ctx) {
    yield* ctx.tool.transform((tools) => {
      tools.add({
        name: "echo",
        description: "返回收到的文字",
        input: Schema.Struct({ text: Schema.String }),
        output: Schema.Struct({ text: Schema.String }),
        options: { codemode: false },
        execute: ({ text }) =>
          Effect.succeed({
            output: { text },
            content: text,
          }),
      })
    })
  }),
})

Поля результата различаются по назначению:output — структурированное значение для программного использования после объявления выходной Schema;content передаётся модели и входит в содержимое сеанса;metadata содержит ограниченное дополнительное состояние. Возврат output без объявления выходной Schema считается ошибкой в текущей реализации.

В исходниках есть два нюанса валидации, более конкретных, чем названия интерфейсов:

  • tool.execute.before выполняется раньше декодирования Schema внутри функции инструмента. Поэтому Hook должен считать вход неизвестной структурой. После изменений Hook вход поступает на последующее декодирование.
  • Effect Schema и поддерживаемые Standard Schema проверяют вход во время выполнения; ветка чистой JSON Schema передаёт вход напрямую. Выходная ветка чистой JSON Schema проверяет лишь, является ли результат JSON-значением, но не каждое объявленное ограничение. Предоставление JSON Schema модели не означает, что сервер проверил параметры бизнес-инструмента.

В текущем канале инструментов ошибка самого предварительного Hook сразу прерывает дальнейшую обработку. Нельзя предполагать, что для такого отказа всегда сработает execute.after . Аудит должен покрывать и пути отказа, а не только события после успешного выполнения.

Ожидаемую восстанавливаемую ошибку инструмента можно отобразить в Tool.Error. Отмена, неизвестный дефект программы и успешный результат должны сохранять разные значения; нельзя поглощать их все и возвращать обычный текст успеха.

Основания: типы инструментов (packages/schema/src/tool.ts), оболочка выполнения инструмента (packages/core/src/tool.ts), фактическая проверка параметров (packages/core/src/tool/runtime.ts).

Пример 2: композиция вызовов через Code Mode

Code Mode определяет представление и композицию части инструментов. Текущая ветка напрямую показывает модели инструменты с codemode: false ; остальные могут попасть в каталог Code Mode, где через execute запускается короткий код для композиции вызовов и обработки результатов. Фактические вызовы из Code Mode всё равно возвращаются в путь выполнения инструментов хоста.

Это подходит для последовательных запросов, фильтрации и агрегации. Среда Code Mode ограничивает прямой доступ к файлам, импорты и подобные операции, но это не означает, что весь сервис OpenCode или внешние плагины работают в системной песочнице. В примере echo выше задано codemode: false, что демонстрирует прямое предоставление инструмента модели.

Запрос к модели фиксирует снимок текущей регистрации инструментов. Горячее обновление не заставляет уже подготовленный запрос использовать новый список; последующие запросы заново подготавливают возможности. Функция инструмента, предоставленная плагином, также не должна зависеть от бесхозных фоновых ресурсов, которые могли быть очищены.

Основания: классификация инструментов и снимки (packages/core/src/tool.ts), Code Mode (packages/core/src/codemode/tool.ts), проверка доступности инструментов текущего запроса (packages/core/src/session/model-request.ts).

Location плагина

Экземпляры плагинов управляются по Location; постоянное бизнес-состояние нужно хранить отдельно. Location — контекст выполнения из каталога и необязательного нативного ID Workspace. Один процесс OpenCode может обслуживать несколько Location одновременно, и один плагин может отдельно активироваться в каждом.

У каждого экземпляра есть Scope, которому принадлежат Transform, Hook, регистрации инструментов и правильно привязанные ресурсы. При закрытии Scope регистрации очищаются. Таймеры плагина, сетевые подписки и наблюдение файлов тоже нужно связать с очисткой. Promise-плагин может из setup вернуть cleanup; Effect-плагин использует соответствующие scoped-ресурсы и finalizer.

首次加载 → 创建 Scope → 注册能力与钩子 → 服务会话
文件或配置变化 → 替换插件 → 清理旧 Scope → 激活新版本
新版本激活失败 → 尝试恢复旧版本 → 恢复失败则停用

Для локальных файлов точек входа и источников конфигурации предусмотрено наблюдение изменений. Если явная точка входа — каталог, изменения его внутренних файлов не имеют той же гарантии автоматического горячего обновления. Загрузка, обновление и остановка зависят и от фактического наблюдения и состояния выполнения. Поэтому заявление о поддержке горячего обновления не заменяет проверку нативного списка возможностей.

Восстановление плагина возвращает код и регистрации, но не откатывает побочные эффекты в файлах, базах данных или удалённых сервисах. Задания по расписанию, состояния согласований, числа повторных попыток и ключи идемпотентности должны храниться надёжно. Состояние сеансов в памяти нужно разделять как минимум по sessionID . Глобальные переменные модуля могут совместно использоваться разными экземплярами плагина, поэтому требуют особой осторожности.

Основания: Scope плагина и восстановление (packages/core/src/plugin.ts), наблюдение файлов плагина (packages/core/src/config/plugin/source.ts).

Практические рекомендации для Plugin

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

  • Для изменения промпта, назначения или предела шагов используйте конфигурацию Agent.
  • Для методов работы и знаний используйте Skill.
  • Для новых реальных действий регистрируйте Tool.
  • Чтобы изменить поведение в узле выполнения, используйте соответствующий Hook.
  • Если плагин должен сохранять бизнес-состояние или вызывать внешние сервисы, явно спроектируйте хранение, аутентификацию, транзакции и идемпотентность. Состояние плагина в памяти не должно обеспечивать эти гарантии.

Плагин с одной ответственностью может состоять из одного TS-файла. Разделяйте его на несколько ID только тогда, когда возможности требуют независимого включения, версий или стратегии отказов. Обычные внешние плагины должны зависеть от публичных интерфейсов плагинов, клиента и schema, избегая приватных реализаций Core. При распространении явно укажите совместимые версии хоста и зависимостей, точку входа модуля и способ сборки.

При обновлении Plugin проверяйте как минимум следующее, а не только успешность импорта модуля:

  • Появляются ли Agent, инструменты и навыки в фактическом списке возможностей и исчезает ли их вклад после выгрузки.
  • Соответствует ли ожиданиям итоговое состояние при разных порядках плагинов, перекрытии конфигурации и сбоях горячего обновления.
  • Сохраняют ли вход, выход, отмена и отказ выполнения инструмента правильную семантику.
  • Действительно ли применяются выбор модели и ассистента сеанса; получают ли подчинённые ассистенты ожидаемые разрешения и контекст.
  • Правильно ли пользовательские инструменты и вызываемые ими сервисы проверяют принадлежность сеанса, бизнес-права, переходы состояния и повторные запросы.
  • Восстанавливается ли постоянное состояние после перезапуска сервиса и правильно ли обрабатывается потеря событий реального времени.
  • Точно ли результаты инструментов и события отражают прогресс, завершение, ошибку и отмену.